//
//  AddEditOverrideTableViewController.swift
//  Loop
//
//  Created by Michael Pangburn on 1/2/19.
//  Copyright © 2019 LoopKit Authors. All rights reserved.
//

import UIKit
import HealthKit
import LoopKit


public protocol AddEditOverrideTableViewControllerDelegate: AnyObject {
    func addEditOverrideTableViewController(_ vc: AddEditOverrideTableViewController, didSavePreset preset: TemporaryScheduleOverridePreset)
    func addEditOverrideTableViewController(_ vc: AddEditOverrideTableViewController, didSaveOverride override: TemporaryScheduleOverride)
    func addEditOverrideTableViewController(_ vc: AddEditOverrideTableViewController, didCancelOverride override: TemporaryScheduleOverride)
}

// MARK: - Default Implementations
extension AddEditOverrideTableViewControllerDelegate {
    public func addEditOverrideTableViewController(_ vc: AddEditOverrideTableViewController, didSavePreset preset: TemporaryScheduleOverridePreset) { }
    public func addEditOverrideTableViewController(_ vc: AddEditOverrideTableViewController, didSaveOverride override: TemporaryScheduleOverride) { }
    public func addEditOverrideTableViewController(_ vc: AddEditOverrideTableViewController, didCancelOverride override: TemporaryScheduleOverride) { }
}


private extension TimeInterval {
    static let defaultOverrideDuration: TimeInterval = .hours(1)
}

public final class AddEditOverrideTableViewController: UITableViewController {

    // MARK: - Public configuration API

    public enum InputMode {
        case newPreset                                                  // Creating a new preset
        case editPreset(TemporaryScheduleOverridePreset)                // Editing an existing preset
        case customizePresetOverride(TemporaryScheduleOverridePreset)   // Defining an override relative to an existing preset
        case customOverride                                             // Defining a one-off custom override
        case editOverride(TemporaryScheduleOverride)                    // Editing an active override
        case viewOverride(TemporaryScheduleOverride)                    // Viewing an override
    }

    public enum DismissalMode {
        case dismissModal
        case popViewController
    }

    public var inputMode: InputMode = .newPreset {
        didSet {
            switch inputMode {
            case .newPreset:
                symbol = nil
                name = nil
                targetRange = nil
                insulinNeedsScaleFactor = 1.0
                duration = .finite(.defaultOverrideDuration)
            case .editPreset(let preset), .customizePresetOverride(let preset):
                symbol = preset.symbol
                name = preset.name
                configure(with: preset.settings)
                duration = preset.duration
            case .customOverride:
                symbol = nil
                name = nil
                targetRange = nil
                insulinNeedsScaleFactor = 1.0
                startDate = Date()
                duration = .finite(.defaultOverrideDuration)
            case .editOverride(let override):
                if case .preset(let preset) = override.context {
                    symbol = preset.symbol
                    name = preset.name
                } else {
                    symbol = nil
                    name = nil
                }
                configure(with: override.settings)
                startDate = override.startDate
                duration = override.duration
                enactTrigger = override.enactTrigger
                syncIdentifier = override.syncIdentifier
            case .viewOverride(let override):
                if case .preset(let preset) = override.context {
                    symbol = preset.symbol
                    name = preset.name
                } else {
                    symbol = nil
                    name = nil
                }
                configure(with: override.settings)
                startDate = override.startDate
                duration = override.duration
                syncIdentifier = override.syncIdentifier
            }
        }
    }

    public var customDismissalMode: DismissalMode?

    public weak var delegate: AddEditOverrideTableViewControllerDelegate?

    // MARK: - Override properties

    private let glucoseUnit: HKUnit

    private var symbol: String? { didSet { updateSaveButtonEnabled() } }

    private var name: String? { didSet { updateSaveButtonEnabled() } }

    private var targetRange: DoubleRange? { didSet { updateSaveButtonEnabled() } }

    private var insulinNeedsScaleFactor = 1.0 { didSet { updateSaveButtonEnabled() }}

    private var startDate = Date()

    private var duration: TemporaryScheduleOverride.Duration = .finite(.defaultOverrideDuration)
    
    private var enactTrigger: TemporaryScheduleOverride.EnactTrigger = .local

    private var syncIdentifier = UUID()
    
    private var isConfiguringPreset: Bool {
        switch inputMode {
        case .newPreset, .editPreset:
            return true
        case .customizePresetOverride, .customOverride, .editOverride, .viewOverride:
            return false
        }
    }

    private func configure(with settings: TemporaryScheduleOverrideSettings) {
        if let targetRange = settings.targetRange {
            self.targetRange = DoubleRange(minValue: targetRange.lowerBound.doubleValue(for: glucoseUnit), maxValue: targetRange.upperBound.doubleValue(for: glucoseUnit))
        } else {
            self.targetRange = nil
        }
        insulinNeedsScaleFactor = settings.effectiveInsulinNeedsScaleFactor
    }

    // MARK: - Initialization & view life cycle

    public init(glucoseUnit: HKUnit) {
        self.glucoseUnit = glucoseUnit
        super.init(style: .grouped)
    }
    
    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    public override func viewDidLoad() {
        super.viewDidLoad()

        setupTitle()
        setupBarButtonItems()

        tableView.register(LabeledTextFieldTableViewCell.nib(), forCellReuseIdentifier: LabeledTextFieldTableViewCell.className)
        tableView.register(DoubleRangeTableViewCell.nib(), forCellReuseIdentifier: DoubleRangeTableViewCell.className)
        tableView.register(DecimalTextFieldTableViewCell.nib(), forCellReuseIdentifier: DecimalTextFieldTableViewCell.className)
        tableView.register(InsulinSensitivityScalingTableViewCell.nib(), forCellReuseIdentifier: InsulinSensitivityScalingTableViewCell.className)
        tableView.register(DateAndDurationTableViewCell.nib(), forCellReuseIdentifier: DateAndDurationTableViewCell.className)
        tableView.register(SwitchTableViewCell.self, forCellReuseIdentifier: SwitchTableViewCell.className)
        tableView.register(TextButtonTableViewCell.self, forCellReuseIdentifier: TextButtonTableViewCell.className)
    }

    // MARK: - UITableViewDataSource

    private enum Section: Int, CaseIterable {
        case properties = 0
        case cancel
    }

    private enum PropertyRow: Int, CaseIterable {
        case symbol
        case name
        case insulinNeeds
        case targetRange
        case startDate
        case endDate
        case durationFiniteness
        case duration
    }

    private var propertyRows: [PropertyRow] {
        var rows: [PropertyRow] = {
            if isConfiguringPreset {
                return [.symbol, .name, .insulinNeeds, .targetRange, .durationFiniteness]
            } else if case let .viewOverride(override) = inputMode, override.hasFinished() {
                return [.insulinNeeds, .targetRange, .startDate, .endDate]
            } else {
                return [.insulinNeeds, .targetRange, .startDate, .durationFiniteness]
            }
        }()

        if duration.isFinite {
            rows.append(.duration)
        }

        rows.sort(by: { $0.rawValue < $1.rawValue })
        return rows
    }

    private func propertyRow(for indexPath: IndexPath) -> PropertyRow {
        return propertyRows[indexPath.row]
    }

    private func indexPath(for row: PropertyRow) -> IndexPath? {
        guard let rowIndex = propertyRows.firstIndex(of: row) else {
            return nil
        }
        return IndexPath(row: rowIndex, section: 0)
    }

    public override func numberOfSections(in tableView: UITableView) -> Int {
        if case .editOverride = inputMode {
            return Section.allCases.count
        } else {
            // No cancel button available unless override is already set
            return Section.allCases.count - 1
        }
    }

    public override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        switch Section(rawValue: section)! {
        case .properties:
            return propertyRows.count
        case .cancel:
            return 1
        }
    }

    private lazy var quantityFormatter: QuantityFormatter = {
        let formatter = QuantityFormatter()
        formatter.setPreferredNumberFormatter(for: glucoseUnit)
        return formatter
    }()

    private lazy var overrideSymbolKeyboard: EmojiInputController = {
        let keyboard = OverrideSymbolInputController()
        keyboard.delegate = self
        return keyboard
    }()

    public override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        switch Section(rawValue: indexPath.section)! {
        case .properties:
            switch propertyRow(for: indexPath) {
            case .symbol:
                let cell = tableView.dequeueReusableCell(withIdentifier: LabeledTextFieldTableViewCell.className, for: indexPath) as! LabeledTextFieldTableViewCell
                cell.titleLabel.text = LocalizedString("Symbol", comment: "The text for the custom preset symbol setting")
                cell.textField.text = symbol
                cell.textField.placeholder = SettingsTableViewCell.NoValueString
                cell.maximumTextLength = 2
                cell.customInput = overrideSymbolKeyboard
                cell.delegate = self
                return cell
            case .name:
                let cell = tableView.dequeueReusableCell(withIdentifier: LabeledTextFieldTableViewCell.className, for: indexPath) as! LabeledTextFieldTableViewCell
                cell.titleLabel.text = LocalizedString("Name", comment: "The text for the custom preset name setting")
                cell.textField.text = name
                cell.textField.placeholder = LocalizedString("Running", comment: "The text for the custom preset name field placeholder")
                cell.delegate = self
                return cell
            case .insulinNeeds:
                let cell = tableView.dequeueReusableCell(withIdentifier: InsulinSensitivityScalingTableViewCell.className, for: indexPath) as! InsulinSensitivityScalingTableViewCell
                cell.scaleFactor = insulinNeedsScaleFactor
                cell.delegate = self
                return cell
            case .targetRange:
                let cell = tableView.dequeueReusableCell(withIdentifier: DoubleRangeTableViewCell.className, for: indexPath) as! DoubleRangeTableViewCell
                cell.numberFormatter = quantityFormatter.numberFormatter
                cell.titleLabel.text = LocalizedString("Target Range", comment: "The text for the custom preset target range setting")
                cell.range = targetRange
                cell.unitLabel.text = quantityFormatter.string(from: glucoseUnit)
                cell.delegate = self
                return cell
            case .startDate:
                let cell = tableView.dequeueReusableCell(withIdentifier: DateAndDurationTableViewCell.className, for: indexPath) as! DateAndDurationTableViewCell
                cell.titleLabel.text = LocalizedString("Start Time", comment: "The text for the custom preset start time")
                cell.datePicker.datePickerMode = .dateAndTime
                cell.datePicker.preferredDatePickerStyle = .wheels
                cell.datePicker.minimumDate = min(startDate, Date())
                cell.date = startDate
                cell.delegate = self
                return cell
            case .endDate:
                guard case let .viewOverride(override) = inputMode else {
                    fatalError("endDate should only be used when viewing override history")
                }
                
                let cell = tableView.dequeueReusableCell(withIdentifier: DateAndDurationTableViewCell.className, for: indexPath) as! DateAndDurationTableViewCell
                cell.titleLabel.text = LocalizedString("End Time", comment: "The text for the override start time")
                cell.datePicker.datePickerMode = .dateAndTime
                cell.date = override.actualEndDate
                return cell
            case .durationFiniteness:
                let cell = tableView.dequeueReusableCell(withIdentifier: SwitchTableViewCell.className, for: indexPath) as! SwitchTableViewCell
                cell.selectionStyle = .none
                cell.textLabel?.text = LocalizedString("Enable Indefinitely", comment: "The text for the indefinite custom preset duration setting")
                cell.switch?.isOn = !duration.isFinite
                cell.switch?.addTarget(self, action: #selector(durationFinitenessChanged), for: .valueChanged)
                return cell
            case .duration:
                let cell = tableView.dequeueReusableCell(withIdentifier: DateAndDurationTableViewCell.className, for: indexPath) as! DateAndDurationTableViewCell
                cell.titleLabel.text = LocalizedString("Duration", comment: "The text for the custom preset duration setting")
                cell.datePicker.datePickerMode = .countDownTimer
                guard case .finite(let duration) = duration else {
                    preconditionFailure("Duration should only be selectable when duration is finite")
                }
                // Use the actual duration if we're retrospectively viewing overrides
                if case let .viewOverride(override) = inputMode {
                    cell.titleLabel.text = LocalizedString("Active Duration", comment: "The text for the override history duration")
                    cell.datePicker.minuteInterval = 1
                    cell.duration = override.actualEndDate.timeIntervalSince(override.startDate)
                } else {
                    cell.titleLabel.text = LocalizedString("Duration", comment: "The text for the override duration setting")
                    cell.datePicker.minuteInterval = 15
                    cell.duration = duration
                }
                cell.maximumDuration = .hours(24)
                cell.delegate = self
                return cell
            }
        case .cancel:
            let cell = tableView.dequeueReusableCell(withIdentifier: TextButtonTableViewCell.className, for: indexPath) as! TextButtonTableViewCell
            if startDate > Date() {
                cell.textLabel?.text = LocalizedString("Cancel", comment: "The text for the scheduled custom preset cancel button")
            } else {
                cell.textLabel?.text = LocalizedString("Disable Preset", comment: "The text for the custom preset disable button")
            }
            cell.textLabel?.textAlignment = .center
            cell.tintColor = .defaultButtonTextColor
            return cell
        }
    }

    @objc private func durationFinitenessChanged(_ sender: UISwitch) {
        if sender.isOn {
            setDurationIndefinite()
        } else {
            setDurationFinite()
        }
    }

    private func setDurationIndefinite() {
        guard let durationIndexPath = indexPath(for: .duration) else {
            assertionFailure("Unable to locate duration row")
            return
        }
        duration = .indefinite
        tableView.deleteRows(at: [durationIndexPath], with: .automatic)
    }

    private func setDurationFinite() {
        switch inputMode {
        case .newPreset, .customOverride:
            duration = .finite(.defaultOverrideDuration)
        case .editPreset(let preset), .customizePresetOverride(let preset):
            switch preset.duration {
            case .finite(let interval):
                duration = .finite(interval)
            case .indefinite:
                duration = .finite(.defaultOverrideDuration)
            }
        case .editOverride(let override), .viewOverride(let override):
            if case .preset(let preset) = override.context,
                case .finite(let interval) = preset.duration {
                duration = .finite(interval)
            } else {
                switch override.duration {
                case .finite(let interval):
                    duration = .finite(interval)
                case .indefinite:
                    duration = .finite(.defaultOverrideDuration)
                }
            }
        }

        guard let durationIndexPath = indexPath(for: .duration) else {
            assertionFailure("Unable to locate duration row")
            return
        }
        tableView.insertRows(at: [durationIndexPath], with: .automatic)
    }

    public override func tableView(_ tableView: UITableView, titleForFooterInSection section: Int) -> String? {
        guard section == 0 else {
            return nil
        }

        switch inputMode {
        case .customizePresetOverride(let preset):
            return String(format: LocalizedString("Changes will only apply this time you enable the preset. The default settings of %@ will not be affected.", comment: "Footer text for customizing from a preset (1: preset name)"), preset.name)
        case .editOverride(let override):
            guard case .preset(let preset) = override.context else {
                return nil
            }
            return String(format: LocalizedString("Edits persist only until the preset is disabled. The default settings of %@ will not be affected.", comment: "Footer text for editing an enabled custom preset (1: preset name)"), preset.name)
        default:
            return nil
        }
    }

    // MARK: - UITableViewDelegate

    public override func tableView(_ tableView: UITableView, willSelectRowAt indexPath: IndexPath) -> IndexPath? {
        switch Section(rawValue: indexPath.section)! {
        case .properties:
            tableView.endEditing(false)
            tableView.beginUpdates()
            collapseExpandableCells(excluding: indexPath)
        case .cancel:
            break
        }

        return indexPath
    }

    public override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        switch Section(rawValue: indexPath.section)! {
        case .properties:
            tableView.endUpdates()
            tableView.deselectRow(at: indexPath, animated: true)

            if let cell = tableView.cellForRow(at: indexPath) as? LabeledTextFieldTableViewCell, !cell.isFirstResponder {
                cell.textField.becomeFirstResponder()
            }
        case .cancel:
            guard case .editOverride(let override) = inputMode else {
                assertionFailure("Only an already-set override can be canceled")
                return
            }
            delegate?.addEditOverrideTableViewController(self, didCancelOverride: override)
            dismiss()
        }
    }

    private func collapseExpandableCells(excluding indexPath: IndexPath? = nil) {
        tableView.beginUpdates()
        hideDatePickerCells(excluding: indexPath)
        collapseInsulinSensitivityScalingCells(excluding: indexPath)
        tableView.endUpdates()
    }
}

// MARK: - Navigation item configuration

extension AddEditOverrideTableViewController {
    private func setupTitle() {
        if let symbol = symbol, let name = name {
            let format = LocalizedString("%1$@ %2$@", comment: "The format for a preset symbol and name (1: symbol)(2: name)")
            title = String(format: format, symbol, name)
        } else {
            switch inputMode {
            case .newPreset:
                title = LocalizedString("New Preset", comment: "The title for the new custom preset entry screen")
            case .editPreset, .customizePresetOverride:
                assertionFailure("Editing or customizing a preset means we'll have a symbol and a name")
            case .customOverride:
                title = LocalizedString("Custom Preset", comment: "The title for the custom preset entry screen")
            case .editOverride:
                title = LocalizedString("Edit", comment: "The title for the enabled custom preset editing screen")
            case .viewOverride:
                title = LocalizedString("View Override", comment: "The title for the override editing screen")
            }
        }
    }

    private func setupBarButtonItems() {
        switch inputMode {
        case .newPreset, .editPreset, .editOverride:
            navigationItem.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: .save, target: self, action: #selector(save))
        case .customizePresetOverride, .customOverride:
            navigationItem.rightBarButtonItem = UIBarButtonItem(title: LocalizedString("Enable", comment: "The button text for enabling a temporary override"), style: .done, target: self, action: #selector(save))
        case .viewOverride: break
        }

        updateSaveButtonEnabled()

        switch inputMode {
        case .newPreset:
            navigationItem.leftBarButtonItem = UIBarButtonItem(barButtonSystemItem: .cancel, target: self, action: #selector(cancel))
        default:
            break
        }
    }

    private var configuredSettings: TemporaryScheduleOverrideSettings? {
        if let targetRange = targetRange {
            guard targetRange.maxValue >= targetRange.minValue else {
                return nil
            }
        } else {
            guard insulinNeedsScaleFactor != 1.0 else {
                return nil
            }
        }

        return TemporaryScheduleOverrideSettings(
            unit: glucoseUnit,
            targetRange: targetRange,
            insulinNeedsScaleFactor: insulinNeedsScaleFactor == 1.0 ? nil : insulinNeedsScaleFactor
        )
    }

    private var configuredPreset: TemporaryScheduleOverridePreset? {
        guard
            let symbol = symbol, !symbol.isEmpty,
            let name = name, !name.isEmpty,
            let settings = configuredSettings
        else {
            return nil
        }

        let id: UUID
        if case .editPreset(let preset) = inputMode {
            id = preset.id
        } else {
            id = UUID()
        }

        return TemporaryScheduleOverridePreset(id: id, symbol: symbol, name: name, settings: settings, duration: duration)
    }

    private var configuredOverride: TemporaryScheduleOverride? {
        guard let settings = configuredSettings else {
            return nil
        }

        let context: TemporaryScheduleOverride.Context
        switch inputMode {
        case .customizePresetOverride(let preset):
            let customizedPreset = TemporaryScheduleOverridePreset(
                symbol: preset.symbol,
                name: preset.name,
                settings: settings,
                duration: duration
            )
            context = .preset(customizedPreset)
        case .editOverride(let override), .viewOverride(let override):
            context = override.context
        case .customOverride:
            context = .custom
        case .newPreset, .editPreset:
            assertionFailure()
            return nil
        }

        return TemporaryScheduleOverride(context: context, settings: settings, startDate: startDate, duration: duration, enactTrigger: enactTrigger, syncIdentifier: syncIdentifier)
    }

    private func updateSaveButtonEnabled() {
        navigationItem.rightBarButtonItem?.isEnabled = {
            switch inputMode {
            case .newPreset, .editPreset:
                return configuredPreset != nil
            case .customizePresetOverride, .customOverride, .editOverride:
                return configuredOverride != nil
            case .viewOverride:
                return false
            }
        }()
    }

    @objc private func save() {
        switch inputMode {
        case .newPreset, .editPreset:
            guard let configuredPreset = configuredPreset else {
                assertionFailure("Save button cannot be tapped when preset is invalid")
                break
            }
            delegate?.addEditOverrideTableViewController(self, didSavePreset: configuredPreset)
        case .customizePresetOverride, .customOverride, .editOverride:
            guard let configuredOverride = configuredOverride else {
                assertionFailure("Save button cannot be tapped when override is invalid")
                break
            }
            delegate?.addEditOverrideTableViewController(self, didSaveOverride: configuredOverride)
        case .viewOverride: break
        }
        dismiss()
    }

    @objc private func cancel() {
        dismiss()
    }

    private func dismiss() {
        if let customDismissalMode = customDismissalMode {
            dismiss(with: customDismissalMode)
        } else {
            switch inputMode {
            case .newPreset, .customizePresetOverride, .customOverride:
                dismiss(with: .dismissModal)
            case .editPreset, .editOverride, .viewOverride:
                dismiss(with: .popViewController)
            }
        }
    }

    private func dismiss(with mode: DismissalMode) {
        switch mode {
        case .dismissModal:
            dismiss(animated: true)
        case .popViewController:
            assert(navigationController != nil)
            navigationController?.popViewController(animated: true)
        }
    }
}

// MARK: - Delegation

extension AddEditOverrideTableViewController: TextFieldTableViewCellDelegate {
    public func textFieldTableViewCellDidBeginEditing(_ cell: TextFieldTableViewCell) {
        collapseExpandableCells()
    }

    public func textFieldTableViewCellDidEndEditing(_ cell: TextFieldTableViewCell) {
        updateWithText(from: cell)
    }

    public func textFieldTableViewCellDidChangeEditing(_ cell: TextFieldTableViewCell) {
        updateWithText(from: cell)
    }

    private func updateWithText(from cell: TextFieldTableViewCell) {
        guard let indexPath = tableView.indexPath(for: cell) else {
            return
        }

        switch propertyRow(for: indexPath) {
        case .symbol:
            symbol = cell.textField.text
        case .name:
            name = cell.textField.text
        default:
            assertionFailure()
        }
    }
}

extension AddEditOverrideTableViewController: EmojiInputControllerDelegate {
    public func emojiInputControllerDidAdvanceToStandardInputMode(_ controller: EmojiInputController) {
        guard
            let indexPath = indexPath(for: .symbol),
            let cell = tableView.cellForRow(at: indexPath) as? LabeledTextFieldTableViewCell,
            let textField = cell.textField as? CustomInputTextField
        else {
            return
        }

        let customInput = textField.customInput
        textField.customInput = nil
        textField.resignFirstResponder()
        textField.becomeFirstResponder()
        textField.customInput = customInput
    }
}

extension AddEditOverrideTableViewController: InsulinSensitivityScalingTableViewCellDelegate {
    func insulinSensitivityScalingTableViewCellDidUpdateScaleFactor(_ cell: InsulinSensitivityScalingTableViewCell) {
        insulinNeedsScaleFactor = cell.scaleFactor
    }
}

extension AddEditOverrideTableViewController: DatePickerTableViewCellDelegate {
    public func datePickerTableViewCellDidUpdateDate(_ cell: DatePickerTableViewCell) {
        guard let indexPath = tableView.indexPath(for: cell) else { return }
        switch propertyRow(for: indexPath) {
        case .startDate:
            startDate = cell.date
        case .duration:
            duration = .finite(cell.duration)
        default:
            assertionFailure()
        }
    }
}

extension AddEditOverrideTableViewController: DoubleRangeTableViewCellDelegate {
    func doubleRangeTableViewCellDidBeginEditing(_ cell: DoubleRangeTableViewCell) {
        collapseExpandableCells()
    }

    func doubleRangeTableViewCellDidUpdateRange(_ cell: DoubleRangeTableViewCell) {
        guard let indexPath = tableView.indexPath(for: cell) else { return }
        switch propertyRow(for: indexPath) {
        case .targetRange:
            targetRange = cell.range
        default:
            assertionFailure()
        }
    }
}

private extension UIColor {
    static let defaultButtonTextColor = UIButton(type: .system).titleColor(for: .normal)
}

private extension UIFont {
    func bold() -> UIFont? {
        guard let descriptor = fontDescriptor.withSymbolicTraits(.traitBold) else {
            return nil
        }
        return UIFont(descriptor: descriptor, size: pointSize)
    }
}
