//
//  LocalizationUtil.swift
//  Astro
//
//  Created by Jason Voll on 2016-06-27.
//  Copyright © 2016 Mobify Research & Development Inc. All rights reserved.
//

import Foundation

private extension String {

    // Note: this is case sensitive, make sure English resources are in folder named "Base" (as per default)
    private static let baseResourceName = "Base"
    private static var resourceBundles = [String: Bundle]()

    func localized(to languageCode: String) -> String {
        // "en" is our base language, we never have a separate bundle for English
        let resourceName = languageCode == "en" ? .baseResourceName : languageCode

        if let translatedString = translatedString(inResource: resourceName) {
            return translatedString
        }

        // If we have not returned by this point, they key is not defined for the language
        // we are trying. Try to lookup a string in the base language (if we haven't already).
        if resourceName != .baseResourceName {
            if let translatedString = translatedString(inResource: .baseResourceName) {
                return translatedString
            }
        }

        AstroLog.logger(AstroLog.Localization).error("Failed to lookup translation for key: \(self)")
        // Fallback, return the key like NSLocalizedString does
        return self
    }

    private func translatedString(inResource resourceName: String) -> String? {
        // Try the app bundle first
        if let appBundleResult = stringResourceFromBundle(getAppResourcePath(resourceName)), appBundleResult != self {
            return appBundleResult
        }

        // Try the astro bundle second
        if let astroBundleResult = stringResourceFromBundle(getAstroResourcePath(resourceName)), astroBundleResult != self {
            return astroBundleResult
        }

        return nil
    }

    private func getAppResourcePath(_ resourceName: String) -> String? {
        return AstroApplication.mainBundle.path(forResource: resourceName, ofType: "lproj")
    }

    private func getAstroResourcePath(_ resourceName: String) -> String? {
        return AstroApplication.astroBundle.path(forResource: resourceName, ofType: "lproj")
    }

    private func stringResourceFromBundle(_ resourcePath: String?) -> String? {
        guard let path = resourcePath else {
            return nil
        }
        if let bundle = String.resourceBundles[path] {
            return NSLocalizedString(self, tableName: nil, bundle: bundle, value: "", comment: "")
        }
        guard let bundle = Bundle(path: path) else {
            return nil
        }
        String.resourceBundles[path] = bundle
        return NSLocalizedString(self, tableName: nil, bundle: bundle, value: "", comment: "")
    }
}

// LocaleChangedListeners will be notified whenever the application's Locale
// is changed and should update themselves accordingly.
public protocol LocaleChangedListener: AnyObject {
    func localeDidChange(newLocale: Locale)
}

// Borrowed shamelessly from http://stackoverflow.com/questions/24127587/how-do-i-declare-an-array-of-weak-references-in-swift
class WeakLocaleChangedListenerReference {
    weak var value: LocaleChangedListener?
    init (value: LocaleChangedListener?) {
        self.value = value
    }
}

public struct Locale {
    public let language: String
    public let country: String
    init(language: String, country: String) {
        self.language = language.lowercased()
        self.country = country.uppercased()
    }
}

public struct Localization {

    private static var localeChangedListeners = [WeakLocaleChangedListenerReference]()

    public static func addLocaleChangedListener(_ listener: LocaleChangedListener) {
        localeChangedListeners.append(WeakLocaleChangedListenerReference(value: listener))
    }

    private static let savedLanguageKey = "astro:localization:language"
    static var language: String {
        get {
            if let language = UserDefaults.standard.object(forKey: Localization.savedLanguageKey) as? String {
                return language
            }
            return (Foundation.Locale.current as NSLocale).object(forKey: .languageCode) as! String
        }
        set {
            locale = Locale(language: newValue, country: country)
        }
    }

    private static let savedCountryKey = "astro:localization:country"
    static var country: String {
        get {
            if let country = UserDefaults.standard.object(forKey: Localization.savedCountryKey) as? String {
                return country
            }
            return (Foundation.Locale.current as NSLocale).object(forKey: .countryCode) as! String
        }
        set {
            locale = Locale(language: language, country: newValue)
        }
    }

    public static var locale: Locale {
        get {
            return Locale(language: language, country: country)
        }
        set {
            UserDefaults.standard.set(newValue.language, forKey: Localization.savedLanguageKey)
            UserDefaults.standard.set(newValue.country, forKey: Localization.savedCountryKey)
            UserDefaults.standard.synchronize()

            for listenerReference in localeChangedListeners {
                if let listener = listenerReference.value {
                    listener.localeDidChange(newLocale: locale)
                }
            }
        }
    }

    public static var isLeftToRight: Bool {
        return UIApplication.shared.userInterfaceLayoutDirection == .leftToRight
    }

    public static var isRightToLeft: Bool {
        return UIApplication.shared.userInterfaceLayoutDirection == .rightToLeft
    }

    public static var translate: (String) -> String = { key in
        return key.localized(to: language)
    }
}
