//
//  MINTLEncodingManager.swift
//  ControlSikenjo
//
//  Created by narita on 2018/12/31.
//  Copyright © 2018 narita. All rights reserved.
//

import Cocoa

// TODO: CFStringEncodingのアクセッサ、 比較演算子の定義
class MINTLTextEncodingItem: NSObject
{
    // MARK: - init methods

    init(with encoding: String.Encoding, enable: Bool)
    {
        super.init()
        
        self.encoding = encoding
        self.enable   = enable
    }

    convenience init(with encoding: CFStringEncoding, enable: Bool)
    {
        self.init(with:
            String.Encoding(rawValue: CFStringConvertEncodingToNSStringEncoding(encoding)),
                  enable: enable)
    }

    override convenience init()
    {
        self.init(with: .ascii, enable: false)
    }

    // MARK: - compare methods (in pairs)
    // sortメソッドで使うために定義した
    
    static func encodingCompare(lhs: MINTLTextEncodingItem, rhs: MINTLTextEncodingItem) -> Bool
    {
        let first: CFStringEncoding  = lhs.cfEncoding
        let second: CFStringEncoding = rhs.cfEncoding
        
        let macEncodingForFirst: CFStringEncoding = CFStringGetMostCompatibleMacStringEncoding(first)
        let macEncodingForSecond: CFStringEncoding = CFStringGetMostCompatibleMacStringEncoding(second)
        
        if first == second
        {
            return true // Should really never happen
        }
        
        if (
                macEncodingForFirst == CFStringBuiltInEncodings.unicode.rawValue
            ||
                macEncodingForSecond == CFStringBuiltInEncodings.unicode.rawValue
            )
        {
            if (macEncodingForSecond == macEncodingForFirst)
            {
                    return (first > second) ? false : true;    // Both Unicode; compare second order
            }
            
            return (macEncodingForFirst == CFStringBuiltInEncodings.unicode.rawValue ) ? true : false;    // First is Unicode
        }
        
        if (
                (macEncodingForFirst > macEncodingForSecond)
                ||
            (
                (macEncodingForFirst == macEncodingForSecond) && (first > second)
            )
        )
        {
            return false
        }
        
        return true
    }
    
    // MARK: - accessor methods (in pairs)

    var encoding: String.Encoding = .ascii

    var cfEncoding: CFStringEncoding
    {
        return CFStringConvertNSStringEncodingToEncoding(self.encoding.rawValue)
    }
    
    var macEncoding: CFStringEncoding
    {
        return CFStringGetMostCompatibleMacStringEncoding(self.cfEncoding)
    }

    @objc dynamic var encodingName: String
    {
        return String.localizedName(of: self.encoding)
    }
    
    @objc dynamic var encodingNumber: NSNumber
    {
        return NSNumber(value: self.encoding.rawValue)
    }

    @objc dynamic var cfEncodingNumber: NSNumber
    {
        return NSNumber(value: self.cfEncoding)
    }
    
    @objc dynamic var enable: Bool = false

}

class MINTLEncodingManager: NSObject
{
    static let EncodingsListChanged = NSNotification.Name("EncodingsListChanged")
    
    // MARK: - class methods
    
    static var singletonSharedInstance : MINTLEncodingManager? = nil
    
    class func sharedInstance() -> MINTLEncodingManager?
    {
        if( singletonSharedInstance == nil )
        {
            singletonSharedInstance = MINTLEncodingManager()
        }
        
        return singletonSharedInstance
    }
    
    // MARK: - init methods
    
    override init()
    {
        super.init()

        self.internalEncodings = NSMutableArray()
        
        self.internalEncodings?.addObjects(from: self.allAvailableStringEncodings() as! [Any] )
        
        self.readFromUserDefaults()
    }
    
    func allAvailableStringEncodings() -> NSArray
    {
        let theAvailableencodings: [String.Encoding] = String.availableStringEncodings
        
        // MINTLTextEncodingItemの配列へ変更
        let theMintEncodings = theAvailableencodings.map()
        {
            return MINTLTextEncodingItem(with: $0, enable: false)
        }
        .sorted()
        {
            return MINTLTextEncodingItem.encodingCompare(lhs: $0, rhs: $1)
        }
                
        return theMintEncodings as NSArray
    }
    
    func readFromUserDefaults()
    {
        assert(self.internalEncodings != nil)
        
        let isEncodingsNil: Bool
        let theEnableEncodings: [CFStringEncoding]
        
        if let theEncodings = UserDefaults.standard.array(forKey: "Encodings")
        {
            theEnableEncodings = theEncodings.map()
            {
                $0 as! NSNumber
            }
            .map()
            {
                CFStringEncoding($0.uint32Value)
            }
            
            isEncodingsNil = false
        }
        else
        {
            // 初回か、UserDefaultから"Encodings"を削除した後に実行される
            theEnableEncodings = [ CFStringBuiltInEncodings.unicode.rawValue,
                    CFStringBuiltInEncodings.UTF8.rawValue,
                    CFStringBuiltInEncodings.macRoman.rawValue,
                    CFStringBuiltInEncodings.windowsLatin1.rawValue,
                    UInt32(CFStringEncodings.macJapanese.rawValue),
                    UInt32(CFStringEncodings.shiftJIS.rawValue),
                    UInt32(CFStringEncodings.macChineseTrad.rawValue),
                    UInt32(CFStringEncodings.macKorean.rawValue),
                    UInt32(CFStringEncodings.macChineseSimp.rawValue),
                    UInt32(CFStringEncodings.GB_18030_2000.rawValue)]
            
            isEncodingsNil = true
        }
        
        self.internalEncodings!
            .map()
            {
                $0 as! MINTLTextEncodingItem
            }
            .forEach()
            {
                $0.enable = theEnableEncodings.contains($0.cfEncoding)
            }
        
        if isEncodingsNil
        {
            self.writeToUserDefaults()
        }
    }
    
    func writeToUserDefaults()
    {
        assert(self.internalEncodings != nil)
        
        let theEncodings = self.internalEncodings!.map()
                            {
                                return $0 as! MINTLTextEncodingItem
                            }
                            .filter()
                            {
                                return $0.enable == true
                            }
                            .map()
                            {
                                return $0.cfEncodingNumber
                            }
        
        UserDefaults.standard.set(theEncodings, forKey: "Encodings")
        
        NotificationCenter.default.post(name: MINTLEncodingManager.EncodingsListChanged,
                                        object: self)
    }
    
    // MARK: - dealloc
    
    // MARK: - NSCopying, hash, isEqual:
    
    // MARK: - NSCoding methods
    
    // MARK: - restorableState methods
    
    // MARK: - life cycle methods
    
    // MARK: - action methods
    
    @IBAction func showEncodingPanel(_ sender : Any?)
    {
        let theInstance = MINTLEncodingPanelController.sharedInstance()
        
        theInstance?.showEncodingPanel(sender)
    }
    
    @objc func canShowEncodingPanel(_ sender : Any?) -> Bool
    {
        let theInstance = MINTLEncodingPanelController.sharedInstance()

        return theInstance?.canShowEncodingPanel(sender) ?? false
    }

    @IBAction func selectAll(_ sender : Any?)
    {
        self.internalEncodings?.forEach()
        {
            if let i = $0 as? MINTLTextEncodingItem
            {
                i.enable = true
            }
        }
        
        self.writeToUserDefaults()
    }
    
    @objc func canSelectAll(_ sender : Any?) -> Bool
    {
        return true
    }
    
    @IBAction func clearAll(_ sender : Any?)
    {
        self.internalEncodings?.forEach()
        {
            if let i = $0 as? MINTLTextEncodingItem
            {
                i.enable = false
            }
        }
        
        self.writeToUserDefaults()
    }
    
    @objc func canClearAll(_ sender : Any?) -> Bool
    {
        return true
    }

    @IBAction func revertToDefault(_ sender : Any?)
    {
        self.internalEncodings?.forEach()
        {
            if let i = $0 as? MINTLTextEncodingItem
            {
                i.enable = false
            }
        }

        UserDefaults.standard.set(nil, forKey: "Encodings")

        self.readFromUserDefaults()
        self.writeToUserDefaults()

    }
    
    @objc func canRevertToDefault(_ sender : Any?) -> Bool
    {
        return true
    }

    // MARK: - event handling methods
    
    // MARK: - drawing methods
    
    // MARK: - delegate/datasource methods
    
    // MARK: - accessor methods (in pairs)
    
    @objc var internalEncodings: NSMutableArray? = nil
    
    @objc var enabledEncodings: NSArray?
    {
        let theEncodings = self.internalEncodings!.map()
        {
            $0 as! MINTLTextEncodingItem
        }
        .filter()
        {
            $0.enable == true
        }

        return theEncodings as NSArray
    }
    
    // MARK: - Utility methods

    func enabledEncodingsContain(encoding: String.Encoding) -> Bool
    {
        return  self.enabledEncodings?.map()
                {
                    $0 as! MINTLTextEncodingItem
                }
                .filter()
                {
                    $0.encoding == encoding
                }
                .count != 0
    }
    
    func findEncodingItemFrom(encoding: String.Encoding) -> MINTLTextEncodingItem?
    {
        let theEncodings = self.internalEncodings!.map()
        {
            $0 as! MINTLTextEncodingItem
        }
        .filter()
        {
            $0.encoding == encoding
        }

        return theEncodings.first
    }
}
