//
//  Color.swift
//  MVMCoreUI
//
//  Created by Kevin Christiano on 11/26/19.
//  Copyright © 2019 Verizon Wireless. All rights reserved.
//

import UIKit

/*
 UIColor is not supported by Codable. This Color class
 effectively turns UIColor into a primitive class like
 Int and String and can be used the same.
 */
public final class Color: Codable {
    //--------------------------------------------------
    // MARK: - Properties
    //--------------------------------------------------
    
    public let uiColor: UIColor
    
    public var cgColor: CGColor {
        return uiColor.cgColor
    }
    
    public private(set) var hex: String = ""
    public private(set) var name: String = ""
    
    // Color metadata 🎨
    public private(set) var red: CGFloat = 0
    public private(set) var green: CGFloat = 0
    public private(set) var blue: CGFloat = 0
    public private(set) var alpha: CGFloat = 1
    
    public var hexWithHash: String {
        return "#" + hex
    }
    
    //--------------------------------------------------
    // MARK: - Error
    //--------------------------------------------------
    
    enum ColorError: Error {
        case badName(reason: String)
    }
    
    //--------------------------------------------------
    // MARK: - Class Initializers
    //--------------------------------------------------
    
    public init(uiColor: UIColor) {
        self.uiColor = uiColor
        hex = UIColor.hexString(for: uiColor) ?? ""
        determineRGBA()
    }
    
    public init?(uiColor: UIColor?) {
        guard let uiColor = uiColor else { return nil }
           self.uiColor = uiColor
           hex = UIColor.hexString(for: uiColor) ?? ""
           determineRGBA()
       }
    
    init?(name: String) {
        guard let colorTuple = UIColor.names[name] else { return nil }
        self.uiColor = colorTuple.uiColor
        self.hex = colorTuple.hex
        determineRGBA()
    }
    
    public init?(colorString: String) throws {
        let components = try Color.getColorComponents(for: colorString)
        self.uiColor = components.color
        hex = components.hex
        name = components.name ?? ""
        determineRGBA()
    }
    
    //--------------------------------------------------
    // MARK: - Codable Initializers
    //--------------------------------------------------
    
    required public init(from decoder: Decoder) throws {
        let container = try decoder.singleValueContainer()
        let colorString = try container.decode(String.self)
        let components = try Color.getColorComponents(for: colorString)
        self.uiColor = components.color
        hex = components.hex
        name = components.name ?? ""
        determineRGBA()
    }
    
    public func encode(to encoder: Encoder) throws {
        var container = encoder.singleValueContainer()
        try container.encode(hexWithHash)
    }
    
    //--------------------------------------------------
    // MARK: - Methods
    //--------------------------------------------------
    private static func getColorComponents(for colorString: String) throws -> (color: UIColor, hex: String, name: String?) {
        var color: UIColor
        var hex: String
        var name: String?
        if colorString.hasPrefix("#") {
            hex = colorString.replacingOccurrences(of: "#", with: "")
        } else {
            guard let hexString = UIColor.names[colorString]?.hex else { throw ColorError.badName(reason: "Check the spelling of your color.") }
            hex = hexString.replacingOccurrences(of: "#", with: "")
            name = colorString
        }
        
        if hex.count == 8 {
            color = UIColor.getColorWithTransparencyBy(hex: hex)
        } else {
            color = UIColor.getColorBy(hex: hex)
        }
        return (color, hex, name)
    }
    
    public func convertHexToFloat(start: String.Index, end: String.Index) -> CGFloat {
        
        return CGFloat(Int(hex[start..<end], radix: 16) ?? 0)
    }
    
    private func determineRGBA() {
        
        guard !hex.isEmpty else { return }
        
        let redStart = hex.startIndex
        let redEnd = hex.index(redStart, offsetBy: 2)
        red = convertHexToFloat(start: redStart, end: redEnd)
        
        let greenEnd = hex.index(redEnd, offsetBy: 2)
        green = convertHexToFloat(start: redEnd, end: greenEnd)
        
        let blueEnd = hex.index(greenEnd, offsetBy: 2)
        blue = convertHexToFloat(start: greenEnd, end: blueEnd)
        
        if hex.count == 8 {
            let alphaEnd = hex.index(blueEnd, offsetBy: 2)
            alpha = convertHexToFloat(start: blueEnd, end: alphaEnd)
        }
    }
}
