//
//  LegacyInsulinDeliveryTableViewController.swift
//  Naterade
//
//  Created by Nathan Racklyeft on 1/30/16.
//  Copyright © 2016 Nathan Racklyeft. All rights reserved.
//
import UIKit
import LoopKit

private let ReuseIdentifier = "Right Detail"


public final class LegacyInsulinDeliveryTableViewController: UITableViewController {

    @IBOutlet var needsConfigurationMessageView: ErrorBackgroundView!

    @IBOutlet weak var iobValueLabel: UILabel! {
        didSet {
            iobValueLabel.textColor = headerValueLabelColor
        }
    }

    @IBOutlet weak var iobDateLabel: UILabel!

    @IBOutlet weak var totalValueLabel: UILabel! {
        didSet {
            totalValueLabel.textColor = headerValueLabelColor
        }
    }

    @IBOutlet weak var totalDateLabel: UILabel!

    @IBOutlet weak var dataSourceSegmentedControl: UISegmentedControl! {
        didSet {
            let titleFont = UIFont.systemFont(ofSize: 15, weight: .semibold)
            dataSourceSegmentedControl.setTitleTextAttributes([NSAttributedString.Key.font: titleFont], for: .normal)
            dataSourceSegmentedControl.setTitle(LocalizedString("Event History", comment: "Segmented button title for insulin delivery log event history"), forSegmentAt: 0)
            dataSourceSegmentedControl.setTitle(LocalizedString("Reservoir", comment: "Segmented button title for insulin delivery log reservoir history"), forSegmentAt: 1)
        }
    }

    public var enableEntryDeletion: Bool = true
    
    public var doseStore: DoseStore? {
        didSet {
            if let doseStore = doseStore {
                doseStoreObserver = NotificationCenter.default.addObserver(forName: nil, object: doseStore, queue: OperationQueue.main, using: { [weak self] (note) -> Void in

                    switch note.name {
                    case DoseStore.valuesDidChange:
                        if self?.isViewLoaded == true {
                            self?.reloadData()
                        }
                    default:
                        break
                    }
                })
            } else {
                doseStoreObserver = nil
            }
        }
    }
    
    public var headerValueLabelColor: UIColor = .label

    private var updateTimer: Timer? {
        willSet {
            if let timer = updateTimer {
                timer.invalidate()
            }
        }
    }

    public override func viewDidLoad() {
        super.viewDidLoad()

        state = .display
    }

    public override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)

        updateTimelyStats(nil)
    }

    public override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)

        let updateInterval = TimeInterval(minutes: 5)
        let timer = Timer(
            fireAt: Date().dateCeiledToTimeInterval(updateInterval).addingTimeInterval(2),
            interval: updateInterval,
            target: self,
            selector: #selector(updateTimelyStats(_:)),
            userInfo: nil,
            repeats: true
        )
        updateTimer = timer

        RunLoop.current.add(timer, forMode: RunLoop.Mode.default)
    }

    public override func viewWillDisappear(_ animated: Bool) {
        super.viewWillDisappear(animated)

        updateTimer = nil
    }

    public override func viewDidDisappear(_ animated: Bool) {
        super.viewDidDisappear(animated)

        if tableView.isEditing {
            tableView.endEditing(true)
        }
    }

    deinit {
        if let observer = doseStoreObserver {
            NotificationCenter.default.removeObserver(observer)
        }
    }

    public override func setEditing(_ editing: Bool, animated: Bool) {
        super.setEditing(editing, animated: animated)

        if editing && enableEntryDeletion {
            let item = UIBarButtonItem(
                title: LocalizedString("Delete All", comment: "Button title to delete all objects"),
                style: .plain,
                target: self,
                action: #selector(confirmDeletion(_:))
            )
            navigationItem.setLeftBarButton(item, animated: true)
        } else {
            navigationItem.setLeftBarButton(nil, animated: true)
        }
    }

    // MARK: - Data
    private enum State {
        case unknown
        case unavailable(Error?)
        case display
    }

    private var state = State.unknown {
        didSet {
            if isViewLoaded {
                reloadData()
            }
        }
    }

    private enum DataSourceSegment: Int {
        case history = 0
        case reservoir
    }

    private enum Values {
        case reservoir([ReservoirValue])
        case history([PersistedPumpEvent])
    }

    // Not thread-safe
    private var values = Values.reservoir([]) {
        didSet {
            let count: Int

            switch values {
            case .reservoir(let values):
                count = values.count
            case .history(let values):
                count = values.count
            }

            if count > 0 && enableEntryDeletion {
                navigationItem.rightBarButtonItem = self.editButtonItem
            }
        }
    }

    private func reloadData() {
        switch state {
        case .unknown:
            break
        case .unavailable(let error):
            self.tableView.tableHeaderView?.isHidden = true
            self.tableView.tableFooterView = UIView()
            tableView.backgroundView = needsConfigurationMessageView

            if let error = error {
                needsConfigurationMessageView.errorDescriptionLabel.text = String(describing: error)
            } else {
                needsConfigurationMessageView.errorDescriptionLabel.text = nil
            }
        case .display:
            self.tableView.backgroundView = nil
            self.tableView.tableHeaderView?.isHidden = false
            self.tableView.tableFooterView = nil

            switch DataSourceSegment(rawValue: dataSourceSegmentedControl.selectedSegmentIndex)! {
            case .reservoir:
                doseStore?.getReservoirValues(since: Date.distantPast) { (result) in
                    DispatchQueue.main.async { () -> Void in
                        switch result {
                        case .failure(let error):
                            self.state = .unavailable(error)
                        case .success(let reservoirValues):
                            self.values = .reservoir(reservoirValues)
                            self.tableView.reloadData()
                        }
                    }

                    self.updateTimelyStats(nil)
                    self.updateTotal()
                }
            case .history:
                doseStore?.getPumpEventValues(since: Date.distantPast) { (result) in
                    DispatchQueue.main.async { () -> Void in
                        switch result {
                        case .failure(let error):
                            self.state = .unavailable(error)
                        case .success(let pumpEventValues):
                            self.values = .history(pumpEventValues)
                            self.tableView.reloadData()
                        }
                    }

                    self.updateTimelyStats(nil)
                    self.updateTotal()
                }
            }
        }
    }

    @objc func updateTimelyStats(_: Timer?) {
        updateIOB()
    }

    private lazy var iobNumberFormatter: NumberFormatter = {
        let formatter = NumberFormatter()

        formatter.numberStyle = .decimal
        formatter.maximumFractionDigits = 2

        return formatter
    }()

    private lazy var timeFormatter: DateFormatter = {
        let formatter = DateFormatter()

        formatter.dateStyle = .none
        formatter.timeStyle = .short

        return formatter
    }()

    private func updateIOB() {
        if case .display = state {
            doseStore?.insulinOnBoard(at: Date()) { (result) -> Void in
                DispatchQueue.main.async {
                    switch result {
                    case .failure:
                        self.iobValueLabel.text = "…"
                        self.iobDateLabel.text = nil
                    case .success(let iob):
                        self.iobValueLabel.text = self.iobNumberFormatter.string(from: iob.value)
                        self.iobDateLabel.text = String(format: LocalizedString("com.loudnate.InsulinKit.IOBDateLabel", value: "at %1$@", comment: "The format string describing the date of an IOB value. The first format argument is the localized date."), self.timeFormatter.string(from: iob.startDate))
                    }
                }
            }
        }
    }

    private func updateTotal() {
        if case .display = state {
            let midnight = Calendar.current.startOfDay(for: Date())

            doseStore?.getTotalUnitsDelivered(since: midnight) { (result) in
                DispatchQueue.main.async {
                    switch result {
                    case .failure:
                        self.totalValueLabel.text = "…"
                        self.totalDateLabel.text = nil
                    case .success(let result):
                        self.totalValueLabel.text = NumberFormatter.localizedString(from: NSNumber(value: result.value), number: .none)
                        self.totalDateLabel.text = String(format: LocalizedString("com.loudnate.InsulinKit.totalDateLabel", value: "since %1$@", comment: "The format string describing the starting date of a total value. The first format argument is the localized date."), DateFormatter.localizedString(from: result.startDate, dateStyle: .none, timeStyle: .short))
                    }
                }
            }
        }
    }

    private var doseStoreObserver: Any? {
        willSet {
            if let observer = doseStoreObserver {
                NotificationCenter.default.removeObserver(observer)
            }
        }
    }

    @IBAction func selectedSegmentChanged(_ sender: Any) {
        reloadData()
    }

    @IBAction func confirmDeletion(_ sender: Any) {
        guard !deletionPending else {
            return
        }

        let confirmMessage: String

        switch DataSourceSegment(rawValue: dataSourceSegmentedControl.selectedSegmentIndex)! {
        case .reservoir:
            confirmMessage = LocalizedString("Are you sure you want to delete all reservoir values?", comment: "Action sheet confirmation message for reservoir deletion")
        case .history:
            confirmMessage = LocalizedString("Are you sure you want to delete all history entries?", comment: "Action sheet confirmation message for pump history deletion")
        }

        let sheet = UIAlertController(deleteAllConfirmationMessage: confirmMessage) {
            self.deleteAllObjects()
        }
        present(sheet, animated: true)
    }

    private var deletionPending = false

    private func deleteAllObjects() {
        guard !deletionPending else {
            return
        }

        deletionPending = true

        let completion = { (_: DoseStore.DoseStoreError?) -> Void in
            DispatchQueue.main.async {
                self.deletionPending = false
                self.setEditing(false, animated: true)
            }
        }

        switch DataSourceSegment(rawValue: dataSourceSegmentedControl.selectedSegmentIndex)! {
        case .reservoir:
            doseStore?.deleteAllReservoirValues(completion)
        case .history:
            doseStore?.deleteAllPumpEvents(completion)
        }
    }

    // MARK: - Table view data source
    public override func numberOfSections(in tableView: UITableView) -> Int {
        switch state {
        case .unknown, .unavailable:
            return 0
        case .display:
            return 1
        }
    }

    public override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        switch values {
        case .reservoir(let values):
            return values.count
        case .history(let values):
            return values.count
        }
    }

    public override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: ReuseIdentifier, for: indexPath)

        if case .display = state {
            switch self.values {
            case .reservoir(let values):
                let entry = values[indexPath.row]
                let volume = NumberFormatter.localizedString(from: NSNumber(value: entry.unitVolume), number: .decimal)
                let time = timeFormatter.string(from: entry.startDate)

                cell.textLabel?.text = String(format: LocalizedString("%1$@ U", comment: "Reservoir entry (1: volume value)"), volume)
                cell.textLabel?.textColor = .label
                cell.detailTextLabel?.text = time
                cell.accessoryType = .none
                cell.selectionStyle = .none
            case .history(let values):
                let entry = values[indexPath.row]
                let time = timeFormatter.string(from: entry.date)

                if let attributedText = entry.dose?.localizedAttributedDescription {
                    cell.textLabel?.attributedText = attributedText
                } else {
                    cell.textLabel?.text = entry.title
                }
                
                cell.detailTextLabel?.text = time
                cell.accessoryType = entry.isUploaded ? .checkmark : .none
                cell.selectionStyle = .default
            }
        }

        return cell
    }

    public override func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
        return enableEntryDeletion
    }

    public override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
        if editingStyle == .delete, case .display = state {
            switch values {
            case .reservoir(let reservoirValues):
                var reservoirValues = reservoirValues
                let value = reservoirValues.remove(at: indexPath.row)
                self.values = .reservoir(reservoirValues)

                tableView.deleteRows(at: [indexPath], with: .automatic)

                doseStore?.deleteReservoirValue(value) { (_, error) -> Void in
                    if let error = error {
                        DispatchQueue.main.async {
                            self.present(UIAlertController(with: error), animated: true)
                            self.reloadData()
                        }
                    }
                }
            case .history(let historyValues):
                var historyValues = historyValues
                let value = historyValues.remove(at: indexPath.row)
                self.values = .history(historyValues)

                tableView.deleteRows(at: [indexPath], with: .automatic)

                doseStore?.deletePumpEvent(value) { (error) -> Void in
                    if let error = error {
                        DispatchQueue.main.async {
                            self.present(UIAlertController(with: error), animated: true)
                            self.reloadData()
                        }
                    }
                }
            }
        }
    }

    public override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        if case .display = state, case .history(let history) = values {
            let entry = history[indexPath.row]

            let vc = CommandResponseViewController(command: { (completionHandler) -> String in
                var description = [String]()

                description.append(self.timeFormatter.string(from: entry.date))

                if let title = entry.title {
                    description.append(title)
                }

                if let dose = entry.dose {
                    description.append(String(describing: dose))
                }

                if let raw = entry.raw {
                    description.append(raw.hexadecimalString)
                }

                return description.joined(separator: "\n\n")
            })

            vc.title = LocalizedString("Pump Event", comment: "The title of the screen displaying a pump event")

            show(vc, sender: indexPath)
        }
    }

}

fileprivate extension UIAlertController {
    convenience init(deleteAllConfirmationMessage: String, confirmationHandler handler: @escaping () -> Void) {
        self.init(
            title: nil,
            message: deleteAllConfirmationMessage,
            preferredStyle: .actionSheet
        )
        
        addAction(UIAlertAction(
            title: LocalizedString("Delete All", comment: "Button title to delete all objects"),
            style: .destructive,
            handler: { (_) in handler() }
        ))

        addAction(UIAlertAction(
            title: LocalizedString("Cancel", comment: "The title of the cancel action in an action sheet"),
            style: .cancel
        ))
    }
}

extension DoseEntry {

    fileprivate var numberFormatter: NumberFormatter {
        let numberFormatter = NumberFormatter()
        numberFormatter.maximumFractionDigits = DoseEntry.unitsPerHour.maxFractionDigits
        return numberFormatter
    }

    fileprivate var localizedAttributedDescription: NSAttributedString? {
        let font = UIFont.preferredFont(forTextStyle: .body)

        switch type {
        case .bolus:
            let description: String
            if let deliveredUnits = deliveredUnits,
               deliveredUnits != programmedUnits
            {
                description = String(format: LocalizedString("Interrupted %1$@: <b>%2$@</b> of %3$@ %4$@", comment: "Description of an interrupted bolus dose entry (1: title for dose type, 2: value (? if no value) in bold, 3: programmed value (? if no value), 4: unit)"), type.localizedDescription, numberFormatter.string(from: deliveredUnits) ?? "?", numberFormatter.string(from: programmedUnits) ?? "?", DoseEntry.units.shortLocalizedUnitString())
            } else {
                description = String(format: LocalizedString("%1$@: <b>%2$@</b> %3$@", comment: "Description of a bolus dose entry (1: title for dose type, 2: value (? if no value) in bold, 3: unit)"), type.localizedDescription, numberFormatter.string(from: programmedUnits) ?? "?", DoseEntry.units.shortLocalizedUnitString())
            }

            return createAttributedDescription(from: description, with: font)
        case .basal, .tempBasal:
            let description = String(format: LocalizedString("%1$@: <b>%2$@</b> %3$@", comment: "Description of a basal temp basal dose entry (1: title for dose type, 2: value (? if no value) in bold, 3: unit)"), type.localizedDescription, numberFormatter.string(from: unitsPerHour) ?? "?", DoseEntry.unitsPerHour.shortLocalizedUnitString())
            return createAttributedDescription(from: description, with: font)
        case .suspend, .resume:
            let attributes: [NSAttributedString.Key: Any] = [
                .font: font,
                .foregroundColor: UIColor.secondaryLabel
            ]
            return NSAttributedString(string: type.localizedDescription, attributes: attributes)
        }
    }

    fileprivate func createAttributedDescription(from description: String, with font: UIFont) -> NSAttributedString? {
        let descriptionWithFont = String(format:"<style>body{font-family: '-apple-system', '\(font.fontName)'; font-size: \(font.pointSize);}</style>%@", description)

        guard let attributedDescription = try? NSMutableAttributedString(data: Data(descriptionWithFont.utf8), options: [.documentType: NSAttributedString.DocumentType.html, .characterEncoding: NSNumber(value: String.Encoding.utf8.rawValue)], documentAttributes: nil)
        else {
            return nil
        }

        attributedDescription.enumerateAttribute(.font, in: NSRange(location: 0, length: attributedDescription.length)) { value, range, stop in
            // bold font items have a dominate colour
            if let font = value as? UIFont,
               font.fontDescriptor.symbolicTraits.contains(.traitBold)
            {
                attributedDescription.addAttributes([.foregroundColor: UIColor.label], range: range)
            } else {
                attributedDescription.addAttributes([.foregroundColor: UIColor.secondaryLabel], range: range)
            }
        }

        return attributedDescription
    }
}
