//
//  TabBarPlugin.swift
//  Astro
//
//  Created by Justin Vaillancourt on 2015-06-16.
//  Copyright (c) 2015 Mobify Research & Development Inc. All rights reserved.
//

import Foundation

public class TabBarPlugin: Plugin, LocaleChangedListener, ViewPlugin, UITabBarDelegate {
    struct TabBarItemMetadata {
        let id: String
        let title: String
        let image: UIImage
        let selectedImage: UIImage

        static func fromJson(_ json: JSONObject, respond: RPCMethodCallback) -> TabBarItemMetadata? {
            guard let
                id = json["id"] as? String,
                let title = json["title"] as? String,
                let imageUrl = json["imageUrl"] as? String,
                let selectedImageUrl = json["selectedImageUrl"] as? String else {
                    respond(.error("Could not get 'id', 'title', 'imageUrl', or 'selectedImageUrl' key from item json."))
                    return nil
            }

            guard let
                image = AstroFileUtils.image(filePath: imageUrl, respond: respond),
                let selectedImage = AstroFileUtils.image(filePath: selectedImageUrl, respond: respond) else {
                    respond(.error("Fail to obtain image file from 'imageUrl', or 'selectedImageUrl'"))
                    return nil
            }

            return TabBarItemMetadata(id: id, title: title, image: image, selectedImage: selectedImage)
        }
    }

    // Used to style the deselected tab bar items for all TabBarPlugin instances
    // Note that if you change this, you will need to call .setItems on the
    // instance of TabBarPlugin that you want this applied to. Essentially this
    // property is meant not to be changed after launching the app!
    @objc public static var deselectedColor: UIColor?

    private let typedViewController = TabBarViewController()

    @objc public var viewController: UIViewController {
        return typedViewController
    }

    private var tabBar: UITabBar {
        return typedViewController.tabBar
    }

    var tabBarItemsMetadata = [TabBarItemMetadata]()
    var tabBarItems = [UITabBarItem: TabBarItemMetadata]()
    @objc var tabBarItemsLookupById = [String: UITabBarItem]()

    public required init(address: MessageAddress, messageBus: MessageBus, pluginResolver: PluginResolver, options: JSONObject?) {
        super.init(address: address, messageBus: messageBus, pluginResolver: pluginResolver, options: options)

        self.addRpcMethodShim("setItems") { params, respond in
            ////////// This will be autogenerated at some point //////////
            if let items: [JSONObject] = MethodShimUtils.getArg(params, key: "items", respond: respond) {
                self.setItems(items, respond: respond)
            }
            /////////////////////////////////////////////////////////////
        }

        self.addRpcMethodShim("setOpaque") { _, _ in
            ////////// This will be autogenerated at some point //////////
            self.setOpaque()
            /////////////////////////////////////////////////////////////
        }

        self.addRpcMethodShim("setTranslucent") { _, _ in
            ////////// This will be autogenerated at some point //////////
            self.setTranslucent()
            /////////////////////////////////////////////////////////////
        }

        self.addRpcMethodShim("setColor") { params, respond in
            ////////// This will be autogenerated at some point //////////
            if let color: String = MethodShimUtils.getArg(params, key: "color", respond: respond) {
                if let inactiveColor: String? = MethodShimUtils.getOptionalArg(params, key: "inactiveColor", respond: respond) {
                    self.setColor(color, inactiveColor: inactiveColor, respond: respond)
                }
            }
            /////////////////////////////////////////////////////////////
        }

        self.addRpcMethodShim("setBackgroundColor") { params, respond in
            ////////// This will be autogenerated at some point //////////
            if let color: String = MethodShimUtils.getArg(params, key: "color", respond: respond) {
                self.setBackgroundColor(color, respond: respond)
            }
            /////////////////////////////////////////////////////////////
        }

        self.addRpcMethodShim("selectItem") { params, respond in
            ////////// This will be autogenerated at some point //////////
            if let itemId: String = MethodShimUtils.getArg(params, key: "itemId", respond: respond) {
                self.selectItem(itemId, respond: respond)
            }
            /////////////////////////////////////////////////////////////
        }

        self.addRpcMethodShim("showBorder") { _, respond in
            ////////// This will be autogenerated at some point //////////
            self.showBorder(respond)
            /////////////////////////////////////////////////////////////
        }

        self.addRpcMethodShim("hideBorder") { _, respond in
            ////////// This will be autogenerated at some point //////////
            self.hideBorder(respond)
            /////////////////////////////////////////////////////////////
        }

        typedViewController.tabBar.delegate = self
        Localization.addLocaleChangedListener(self)
    }

    // MARK: - UITabBarDelegate

    public func tabBar(_ tabBar: UITabBar, didSelect item: UITabBarItem) {
        if let tabBarItemMetadata = tabBarItems[item] {
            let params: JSONObject = [
                "id": tabBarItemMetadata.id
            ]
            trigger("itemSelect", params: params)
        }
    }

    // MARK: - RPC Methods

    public func localeDidChange(newLocale: Locale) {
        setItems(tabBarItemsMetadata, respond: { _ in })
    }

    // @RpcMethod
    func setItems(_ items: [JSONObject], respond: RPCMethodCallback) {
        tabBarItemsMetadata = [TabBarItemMetadata]()

        // Ensure that all items are parsable before doing anything else.
        for item in items {
            if let tabBarItemMetadata = TabBarItemMetadata.fromJson(item, respond: respond) {
                tabBarItemsMetadata += [tabBarItemMetadata]
            } else {
                // There was an error parsing the item.
                return
            }
        }

        setItems(tabBarItemsMetadata, respond: respond)
    }

    private func setItems(_ tabBarItemsMetadata: [TabBarItemMetadata], respond: RPCMethodCallback) {
        let lastSelectedItemMetadata = tabBar.selectedItem != nil ? tabBarItems[tabBar.selectedItem!] : nil

        // Create `UITabBarItem`s.
        var orderedTabBarItems = [UITabBarItem]()

        tabBarItems.removeAll(keepingCapacity: false)

        for tabBarItemMetadata in tabBarItemsMetadata {
            let localizedTitle = Localization.translate(tabBarItemMetadata.title)
            let tabBarItem = UITabBarItem(title: localizedTitle, image: tabBarItemMetadata.image, selectedImage: tabBarItemMetadata.selectedImage)
            tabBarItems[tabBarItem] = tabBarItemMetadata

            // Populate dictionary for tabBarItem lookup by ID
            tabBarItemsLookupById[tabBarItemMetadata.id] = tabBarItem

            orderedTabBarItems += [tabBarItem]
        }

        // Set new tab bar items.
        tabBar.items = orderedTabBarItems

        // Reset the selected item.
        if let lastSelectedItemMetadata = lastSelectedItemMetadata {
            for (tabBarItem, tabBarItemMetadata) in tabBarItems {
                if tabBarItemMetadata.id == lastSelectedItemMetadata.id {
                    tabBar.selectedItem = tabBarItem
                    break
                }
            }
        }

        if tabBar.selectedItem == nil, let firstTabBarItem = orderedTabBarItems.first {
            tabBar.selectedItem = firstTabBarItem
            tabBar(tabBar, didSelect: firstTabBarItem)
        }

        applyDeselectedColor()
    }

    // @RpcMethod
    @objc func setOpaque() {
        tabBar.isTranslucent = false
    }

    // @RpcMethod
    @objc func setTranslucent() {
        tabBar.isTranslucent = true
    }

    // @RpcMethod
    func setColor(_ color: String, inactiveColor: String? = nil, respond: RPCMethodCallback) {
        if let tintColor = UIColor(hex: color) {
            tabBar.tintColor = tintColor
        } else {
            respond(.error("Invalid hex color provided: '\(color)'."))
        }

        if let inactiveColor = inactiveColor {
            if let inactiveTintColor = UIColor(hex: inactiveColor) {
                let appearance = UITabBarItem.appearance()

                var titleTextAttributes = convertFromOptionalNSAttributedStringKeyDictionary(appearance.titleTextAttributes(for: .normal)) ?? [:]
                titleTextAttributes[NSAttributedString.Key.foregroundColor.rawValue] = inactiveTintColor

                var convertedTitleTextAttributes = [NSAttributedString.Key:Any]()
                for (key, value) in titleTextAttributes {
                    convertedTitleTextAttributes[NSAttributedString.Key.init(key)] = value
                }

                appearance.setTitleTextAttributes(convertedTitleTextAttributes, for: .normal)
            } else {
                respond(.error("Invalid hex inactive color provided: '\(inactiveColor)'."))
            }
        }
    }

    // @RpcMethod
    func setBackgroundColor(_ color: String, respond: RPCMethodCallback) {
        if let backgroundColor = UIColor(hex: color) {
            tabBar.barTintColor = backgroundColor
        } else {
            respond(.error("Invalid hex color provided: '\(color)'."))
        }
    }

    // @RpcMethod
    func selectItem(_ itemId: String, respond: RPCMethodCallback) {
        if let itemToSelect = tabBarItemsLookupById[itemId] {
            tabBar.selectedItem = itemToSelect
            tabBar(tabBar, didSelect: itemToSelect)
        }
    }

    // @RpcMethod
    func showBorder(_ respond: RPCMethodCallback) {
        AstroLog.logger(AstroLog.Plugins).debug("showBorder() is not supported on iOS")
    }

    // @RpcMethod
    func hideBorder(_ respond: RPCMethodCallback) {
        AstroLog.logger(AstroLog.Plugins).debug("hideBorder() is not supported on iOS")
    }

    // MARK: - Helper methods

    private func applyDeselectedColor() {
        // Re-create all of the .image properties using the new deselected color
        if let deselectedColor = TabBarPlugin.deselectedColor,
            let tabBarItems = tabBar.items {
            for item in tabBarItems {
                if let image = item.image {
                    item.image = image.withTint(deselectedColor).withRenderingMode(.alwaysOriginal)
                }
            }
        }
    }
}

// Helper function inserted by Swift 4.2 migrator.
fileprivate func convertFromOptionalNSAttributedStringKeyDictionary(_ input: [NSAttributedString.Key: Any]?) -> [String: Any]? {
    guard let input = input else { return nil }
    return Dictionary(uniqueKeysWithValues: input.map {key, value in (key.rawValue, value)})
}
