//
//  SetConstrainedScheduleEntryTableViewCell.swift
//  LoopKitUI
//
//  Created by Pete Schwamb on 2/23/19.
//  Copyright © 2019 LoopKit Authors. All rights reserved.
//

import Foundation
import HealthKit
import LoopKit
import UIKit

protocol SetConstrainedScheduleEntryTableViewCellDelegate: AnyObject {
    func setConstrainedScheduleEntryTableViewCellDidUpdate(_ cell: SetConstrainedScheduleEntryTableViewCell)
}

private enum Component: Int, CaseIterable {
    case time = 0
    case value
}

class SetConstrainedScheduleEntryTableViewCell: UITableViewCell {

    public enum EmptySelectionType {
        case none
        case firstIndex
        case lastIndex

        var rowCount: Int {
            if self == .none {
                return 0
            } else {
                return 1
            }
        }

        var rowOffset: Int {
            if self == .firstIndex {
                return 1
            } else {
                return 0
            }
        }
    }

    @IBOutlet private weak var picker: UIPickerView!

    @IBOutlet private weak var pickerHeightConstraint: NSLayoutConstraint!

    private var pickerExpandedHeight: CGFloat = 0

    @IBOutlet private weak var dateLabel: UILabel!

    @IBOutlet private weak var valueLabel: UILabel!

    public weak var delegate: SetConstrainedScheduleEntryTableViewCellDelegate?

    public var allowedValues: [Double] = [] {
        didSet {
            picker.reloadAllComponents()
            updateValuePicker()
        }
    }

    public var emptySelectionType = EmptySelectionType.none {
        didSet {
            picker.reloadAllComponents()
            updateValuePicker()
        }
    }

    public var unit: HKUnit? {
        didSet {
            if let unit = unit {
                valueQuantityFormatter.setPreferredNumberFormatter(for: unit)
                picker.reloadAllComponents()
                updateValuePicker()
            }
        }
    }

    public var minimumTimeInterval: TimeInterval = .hours(0.5)

    public var minimumStartTime: TimeInterval = .hours(0) {
        didSet {
            picker.reloadComponent(Component.time.rawValue)
            updateStartTimeSelection()
        }
    }
    public var maximumStartTime: TimeInterval = .hours(23.5) {
        didSet {
            picker.reloadComponent(Component.time.rawValue)
        }
    }

    public var timeZone: TimeZone! {
        didSet {
            dateFormatter.timeZone = timeZone
            var calendar = Calendar.current
            calendar.timeZone = timeZone
            startOfDay = calendar.startOfDay(for: Date())
        }
    }

    private lazy var startOfDay = Calendar.current.startOfDay(for: Date())

    var startTime: TimeInterval = 0 {
        didSet {
            updateStartTimeSelection()
            updateDateLabel()
        }
    }

    var selectedStartTime: TimeInterval {
        let row = picker.selectedRow(inComponent: Component.time.rawValue)
        return startTimeForTimeComponent(row: row)
    }

    var value: Double? = nil {
        didSet {
            updateValuePicker()
            updateValueLabel()
        }
    }

    var isPickerHidden: Bool {
        get {
            return picker.isHidden
        }
        set {
            picker.isHidden = newValue
            pickerHeightConstraint.constant = newValue ? 0 : pickerExpandedHeight

            if !newValue {
                updateValuePicker()
                updateStartTimeSelection()
            }
        }
    }

    var isReadOnly = false

    override func awakeFromNib() {
        super.awakeFromNib()

        pickerExpandedHeight = pickerHeightConstraint.constant

        valueLabel.text = nil
        setSelected(true, animated: false)
        updateDateLabel()
    }

    override func setSelected(_ selected: Bool, animated: Bool) {
        super.setSelected(selected, animated: animated)

        if selected && !isReadOnly {
            isPickerHidden.toggle()
        }
    }

    private lazy var dateFormatter: DateFormatter = {
        let dateFormatter = DateFormatter()
        dateFormatter.dateStyle = .none
        dateFormatter.timeStyle = .short

        return dateFormatter
    }()

    lazy var valueQuantityFormatter: QuantityFormatter = {
        let formatter = QuantityFormatter()
        return formatter
    }()

    private func startTimeForTimeComponent(row: Int) -> TimeInterval {
        return minimumStartTime + minimumTimeInterval * TimeInterval(row)
    }

    private func stringForStartTime(_ time: TimeInterval) -> String {
        let date = startOfDay.addingTimeInterval(time)
        return dateFormatter.string(from: date)
    }

    func updateDateLabel() {
        dateLabel.text = stringForStartTime(startTime)
    }

    func validate() {
        if let value = value, allowedValues.contains(value) {
            valueLabel.textColor = nil  // Default color
        } else {
            valueLabel.textColor = .systemRed
        }
    }

    func updateValueFromPicker() {
        let index = picker.selectedRow(inComponent: Component.value.rawValue) - emptySelectionType.rowOffset
        if index >= 0 && index < allowedValues.count {
            value = allowedValues[index]
        } else {
            value = nil
        }
        updateValueLabel()
    }

    private func updateStartTimeSelection() {
        let row = Int(round((startTime - minimumStartTime) / minimumTimeInterval))
        if row >= 0 && row < pickerView(picker, numberOfRowsInComponent: Component.time.rawValue) {
            picker.selectRow(row, inComponent: Component.time.rawValue, animated: true)
        }
    }

    func updateValuePicker() {
        guard !allowedValues.isEmpty else {
            return
        }
        let selectedIndex: Int
        if let value = value {
            if let row = allowedValues.firstIndex(of: value) {
                selectedIndex = row + emptySelectionType.rowOffset
            } else {
                // Select next highest value
                selectedIndex = (allowedValues.enumerated().filter({$0.element >= value}).min(by: { $0.1 < $1.1 })?.offset ?? 0) + emptySelectionType.rowOffset
            }
        } else {
            switch emptySelectionType {
            case .none:
                selectedIndex = allowedValues.count - 1
            case .firstIndex:
                selectedIndex = 0
            case .lastIndex:
                selectedIndex = allowedValues.count
            }
        }
        picker.selectRow(selectedIndex, inComponent: Component.value.rawValue, animated: true)
    }

    func updateValueLabel() {
        guard let value = value else {
            valueLabel.text = nil
            return
        }
        validate()
        valueLabel.text = formatValue(value)
    }

    private func formatValue(_ value: Double) -> String? {
        if let unit = unit {
            let quantity = HKQuantity(unit: unit, doubleValue: value)
            return valueQuantityFormatter.string(from: quantity, for: unit)
        } else {
            return valueQuantityFormatter.numberFormatter.string(from: value)
        }
    }
}


extension SetConstrainedScheduleEntryTableViewCell: UIPickerViewDelegate {

    func pickerView(_ pickerView: UIPickerView,
                    didSelectRow row: Int,
                    inComponent component: Int) {
        switch Component(rawValue: component)! {
        case .time:
            startTime = selectedStartTime
        case .value:
            updateValueFromPicker()
        }

        delegate?.setConstrainedScheduleEntryTableViewCellDidUpdate(self)
    }

    func pickerView(_ pickerView: UIPickerView, rowHeightForComponent component: Int) -> CGFloat {
        let metrics = UIFontMetrics(forTextStyle: .body)
        return metrics.scaledValue(for: 32)
    }

    func pickerView(_ pickerView: UIPickerView,
                    titleForRow row: Int,
                    forComponent component: Int) -> String? {

        switch Component(rawValue: component)! {
        case .time:
            let time = startTimeForTimeComponent(row: row)
            return stringForStartTime(time)
        case .value:
            let valueRow = row - emptySelectionType.rowOffset
            guard valueRow >= 0 && valueRow < allowedValues.count else {
                return nil
            }
            return formatValue(allowedValues[valueRow])
        }
    }
}

extension SetConstrainedScheduleEntryTableViewCell: UIPickerViewDataSource {
    func numberOfComponents(in pickerView: UIPickerView) -> Int {
        return Component.allCases.count
    }

    func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
        switch Component(rawValue: component)! {
        case .time:
            return Int(round((maximumStartTime - minimumStartTime) / minimumTimeInterval) + 1)
        case .value:
            return allowedValues.count + emptySelectionType.rowCount
        }
    }
}

/// UITableViewController extensions to aid working with DatePickerTableViewCell
extension SetConstrainedScheduleEntryTableViewCellDelegate where Self: UITableViewController {
    func hideSetConstrainedScheduleEntryCells(excluding indexPath: IndexPath? = nil) {
        for case let cell as SetConstrainedScheduleEntryTableViewCell in tableView.visibleCells where tableView.indexPath(for: cell) != indexPath && cell.isPickerHidden == false {
            cell.isPickerHidden = true
        }
    }
}
