import Foundation
import UIKit
import AppBundle
import AsyncDisplayKit
import Display
import SwiftSignalKit
import OverlayStatusController

private final class WalletCreateInvoiceScreenArguments {
    let context: WalletContext
    let updateState: ((WalletCreateInvoiceScreenState) -> WalletCreateInvoiceScreenState) -> Void
    let updateText: (WalletCreateInvoiceScreenEntryTag, String) -> Void
    let dismissInput: () -> Void
    let scrollToBottom: () -> Void
    
    init(context: WalletContext, updateState: @escaping ((WalletCreateInvoiceScreenState) -> WalletCreateInvoiceScreenState) -> Void, updateText: @escaping (WalletCreateInvoiceScreenEntryTag, String) -> Void, dismissInput: @escaping () -> Void, scrollToBottom: @escaping () -> Void) {
        self.context = context
        self.updateState = updateState
        self.updateText = updateText
        self.dismissInput = dismissInput
        self.scrollToBottom = scrollToBottom
    }
}

private enum WalletCreateInvoiceScreenSection: Int32 {
    case amount
    case comment
}

private enum WalletCreateInvoiceScreenEntryTag: ItemListItemTag {
    case amount
    case comment
    
    func isEqual(to other: ItemListItemTag) -> Bool {
        if let other = other as? WalletCreateInvoiceScreenEntryTag {
            return self == other
        } else {
            return false
        }
    }
}

private enum WalletCreateInvoiceScreenEntry: ItemListNodeEntry {
    case amount(WalletTheme, String)
    case amountInfo(WalletTheme, String)
    case commentHeader(WalletTheme, String)
    case comment(WalletTheme, String, String)
   
    var section: ItemListSectionId {
        switch self {
        case .amount, .amountInfo:
            return WalletCreateInvoiceScreenSection.amount.rawValue
        case .commentHeader, .comment:
            return WalletCreateInvoiceScreenSection.comment.rawValue
        }
    }
    
    var stableId: Int32 {
        switch self {
        case .amount:
            return 0
        case .amountInfo:
            return 1
        case .commentHeader:
            return 2
        case .comment:
            return 3
        }
    }
    
    static func ==(lhs: WalletCreateInvoiceScreenEntry, rhs: WalletCreateInvoiceScreenEntry) -> Bool {
        switch lhs {
        case let .amount(lhsTheme, lhsAmount):
            if case let .amount(rhsTheme, rhsAmount) = rhs, lhsTheme === rhsTheme, lhsAmount == rhsAmount {
                return true
            } else {
                return false
            }
        case let .amountInfo(lhsTheme, lhsText):
            if case let .amountInfo(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
                return true
            } else {
                return false
            }
        case let .commentHeader(lhsTheme, lhsText):
            if case let .commentHeader(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
                return true
            } else {
                return false
            }
        case let .comment(lhsTheme, lhsPlaceholder, lhsText):
            if case let .comment(rhsTheme, rhsPlaceholder, rhsText) = rhs, lhsTheme === rhsTheme, lhsPlaceholder == rhsPlaceholder, lhsText == rhsText {
                return true
            } else {
                return false
            }
        }
    }
    
    static func <(lhs: WalletCreateInvoiceScreenEntry, rhs: WalletCreateInvoiceScreenEntry) -> Bool {
        return lhs.stableId < rhs.stableId
    }
    
    func item(_ arguments: Any) -> ListViewItem {
        let arguments = arguments as! WalletCreateInvoiceScreenArguments
        switch self {
        case let .amount(theme, amount):
            return WalletAmountItem(theme: theme, amount: amount, sectionId: self.section, textUpdated: { text in
                let text = formatAmountText(text, decimalSeparator: arguments.context.presentationData.dateTimeFormat.decimalSeparator)
                arguments.updateText(WalletCreateInvoiceScreenEntryTag.amount, text)
            }, shouldUpdateText: { text in
                return isValidAmount(text)
            }, processPaste: { pastedText in
                if isValidAmount(pastedText) {
                    return normalizedStringForGramsString(pastedText)
                } else {
                    return amount
                }
            }, updatedFocus: { focus in
                arguments.updateState { state in
                    var state = state
                    state.focusItemTag = focus ? WalletCreateInvoiceScreenEntryTag.amount : nil
                    return state
                }
                if !focus {
                    let presentationData = arguments.context.presentationData
                    arguments.updateState { state in
                        var state = state
                        if !state.amount.isEmpty {
                            state.amount = normalizedStringForGramsString(state.amount, decimalSeparator: presentationData.dateTimeFormat.decimalSeparator)
                        }
                        return state
                    }
                }
            }, tag: WalletCreateInvoiceScreenEntryTag.amount)
        case let .amountInfo(theme, text):
            return ItemListTextItem(theme: theme, text: .markdown(text), sectionId: self.section)
        case let .commentHeader(theme, text):
            return ItemListSectionHeaderItem(theme: theme, text: text, sectionId: self.section)
        case let .comment(theme, placeholder, value):
            return ItemListMultilineInputItem(theme: theme, text: value, placeholder: placeholder, maxLength: ItemListMultilineInputItemTextLimit(value: walletTextLimit, display: true, mode: .bytes), sectionId: self.section, style: .blocks, returnKeyType: .done, textUpdated: { text in
                arguments.updateText(WalletCreateInvoiceScreenEntryTag.comment, text)
            }, shouldUpdateText: { text in
                let textLength: Int = text.data(using: .utf8, allowLossyConversion: true)?.count ?? 0
                return text.count <= walletTextLimit
            }, updatedFocus: { focus in
                arguments.updateState { state in
                    var state = state
                    state.focusItemTag = focus ? WalletCreateInvoiceScreenEntryTag.comment : nil
                    return state
                }
                if focus {
                    arguments.scrollToBottom()
                }
            }, tag: WalletCreateInvoiceScreenEntryTag.comment, action: {
                arguments.dismissInput()
            })
        }
    }
}

private struct WalletCreateInvoiceScreenState: Equatable {
    var amount: String
    var comment: String
    var focusItemTag: WalletCreateInvoiceScreenEntryTag?
    
    var isEmpty: Bool {
        return self.amount.isEmpty && self.comment.isEmpty
    }
}

private func walletCreateInvoiceScreenEntries(presentationData: WalletPresentationData, address: String, state: WalletCreateInvoiceScreenState) -> [WalletCreateInvoiceScreenEntry] {
    var entries: [WalletCreateInvoiceScreenEntry] = []
    entries.append(.amount(presentationData.theme, state.amount ?? ""))
    entries.append(.amountInfo(presentationData.theme, presentationData.strings.Wallet_Receive_CreateInvoiceInfo))
    entries.append(.commentHeader(presentationData.theme, presentationData.strings.Wallet_Receive_CommentHeader))
    entries.append(.comment(presentationData.theme, presentationData.strings.Wallet_Receive_CommentInfo, state.comment))
    return entries
}

protocol WalletCreateInvoiceScreen {
    
}

private final class WalletCreateInvoiceScreenImpl: ItemListController, WalletCreateInvoiceScreen {
    override func preferredContentSizeForLayout(_ layout: ContainerViewLayout) -> CGSize? {
        return CGSize(width: layout.size.width, height: min(674.0, layout.size.height))
    }
}

func walletCreateInvoiceScreen(context: WalletContext, address: String) -> ViewController {
    let initialState = WalletCreateInvoiceScreenState(amount: "", comment: "", focusItemTag: nil)
    let statePromise = ValuePromise(initialState, ignoreRepeated: true)
    let stateValue = Atomic(value: initialState)
    let updateState: ((WalletCreateInvoiceScreenState) -> WalletCreateInvoiceScreenState) -> Void = { f in
        statePromise.set(stateValue.modify { f($0) })
    }
    
    var presentControllerImpl: ((ViewController, Any?) -> Void)?
    var pushImpl: ((ViewController) -> Void)?
    var dismissImpl: (() -> Void)?
    var dismissInputImpl: (() -> Void)?
    var ensureItemVisibleImpl: ((WalletCreateInvoiceScreenEntryTag, Bool) -> Void)?
    
    weak var currentStatusController: ViewController?
    let arguments = WalletCreateInvoiceScreenArguments(context: context, updateState: { f in
        updateState(f)
    }, updateText: { tag, value in
        updateState { state in
            var state = state
            switch tag {
            case .amount:
                state.amount = value
            case .comment:
                state.comment = value
            }
            return state
        }
        ensureItemVisibleImpl?(tag, true)
    }, dismissInput: {
        dismissInputImpl?()
    }, scrollToBottom: {
        ensureItemVisibleImpl?(WalletCreateInvoiceScreenEntryTag.comment, true)
    })
    
    let signal = combineLatest(queue: .mainQueue(), .single(context.presentationData), statePromise.get())
    |> map { presentationData, state -> (ItemListControllerState, (ItemListNodeState, Any)) in
        var ensureVisibleItemTag: ItemListItemTag?
        if let focusItemTag = state.focusItemTag {
            ensureVisibleItemTag = focusItemTag
        }
        
        let rightNavigationButton = ItemListNavigationButton(content: .text(presentationData.strings.Wallet_Navigation_Done), style: .bold, enabled: !state.isEmpty, action: {
            pushImpl?(WalletReceiveScreen(context: context, mode: .invoice(address: address, amount: state.amount, comment: state.comment)))
        })
        
        let controllerState = ItemListControllerState(theme: presentationData.theme, title: .text(presentationData.strings.Wallet_CreateInvoice_Title), leftNavigationButton: nil, rightNavigationButton: rightNavigationButton, backNavigationButton: ItemListBackButton(title: presentationData.strings.Wallet_Navigation_Back), animateChanges: false)
        let listState = ItemListNodeState(entries: walletCreateInvoiceScreenEntries(presentationData: presentationData, address: address, state: state), style: .blocks, focusItemTag: ensureVisibleItemTag, ensureVisibleItemTag: ensureVisibleItemTag, animateChanges: false)
        
        return (controllerState, (listState, arguments))
    }
    
    let controller = WalletCreateInvoiceScreenImpl(theme: context.presentationData.theme, strings: context.presentationData.strings, updatedPresentationData: .single((context.presentationData.theme, context.presentationData.strings)), state: signal, tabBarItem: nil, hasNavigationBarSeparator: false)
    controller.supportedOrientations = ViewControllerSupportedOrientations(regularSize: .all, compactSize: .portrait)
    controller.experimentalSnapScrollToItem = true
    controller.didAppear = { _ in
        updateState { state in
            var state = state
            state.focusItemTag = .amount
            return state
        }
    }
    presentControllerImpl = { [weak controller] c, a in
        controller?.present(c, in: .window(.root), with: a)
    }
    pushImpl = { [weak controller] c in
        controller?.push(c)
    }
    dismissImpl = { [weak controller] in
        controller?.view.endEditing(true)
        let _ = controller?.dismiss()
    }
    dismissInputImpl = { [weak controller] in
        controller?.view.endEditing(true)
    }
    ensureItemVisibleImpl = { [weak controller] targetTag, animated in
        controller?.afterLayout({
            guard let controller = controller else {
                return
            }
            var resultItemNode: ListViewItemNode?
            let state = stateValue.with({ $0 })
            let _ = controller.frameForItemNode({ itemNode in
                if let itemNode = itemNode as? ItemListItemNode {
                    if let tag = itemNode.tag, tag.isEqual(to: targetTag) {
                        resultItemNode = itemNode as? ListViewItemNode
                        return true
                    }
                }
                return false
            })
            if let resultItemNode = resultItemNode {
                controller.ensureItemNodeVisible(resultItemNode, animated: animated)
            }
        })
    }
    return controller
}