// Swift port by Devin Abbott <devinabbott@gmail.com>, 2017.

// (c) Dean McNamee <dean@gmail.com>, 2012.
//
// https://github.com/deanm/css-color-parser-js

// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to
// deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.

import Foundation

typealias CSSColor = (r: Int, g: Int, b: Int, a: Double)

// http://www.w3.org/TR/css3-color/
private let kCSSColorTable: [String: CSSColor] = [
    "transparent": (0, 0, 0, 0), "aliceblue": (240, 248, 255, 1),
    "antiquewhite": (250, 235, 215, 1), "aqua": (0, 255, 255, 1),
    "aquamarine": (127, 255, 212, 1), "azure": (240, 255, 255, 1),
    "beige": (245, 245, 220, 1), "bisque": (255, 228, 196, 1),
    "black": (0, 0, 0, 1), "blanchedalmond": (255, 235, 205, 1),
    "blue": (0, 0, 255, 1), "blueviolet": (138, 43, 226, 1),
    "brown": (165, 42, 42, 1), "burlywood": (222, 184, 135, 1),
    "cadetblue": (95, 158, 160, 1), "chartreuse": (127, 255, 0, 1),
    "chocolate": (210, 105, 30, 1), "coral": (255, 127, 80, 1),
    "cornflowerblue": (100, 149, 237, 1), "cornsilk": (255, 248, 220, 1),
    "crimson": (220, 20, 60, 1), "cyan": (0, 255, 255, 1),
    "darkblue": (0, 0, 139, 1), "darkcyan": (0, 139, 139, 1),
    "darkgoldenrod": (184, 134, 11, 1), "darkgray": (169, 169, 169, 1),
    "darkgreen": (0, 100, 0, 1), "darkgrey": (169, 169, 169, 1),
    "darkkhaki": (189, 183, 107, 1), "darkmagenta": (139, 0, 139, 1),
    "darkolivegreen": (85, 107, 47, 1), "darkorange": (255, 140, 0, 1),
    "darkorchid": (153, 50, 204, 1), "darkred": (139, 0, 0, 1),
    "darksalmon": (233, 150, 122, 1), "darkseagreen": (143, 188, 143, 1),
    "darkslateblue": (72, 61, 139, 1), "darkslategray": (47, 79, 79, 1),
    "darkslategrey": (47, 79, 79, 1), "darkturquoise": (0, 206, 209, 1),
    "darkviolet": (148, 0, 211, 1), "deeppink": (255, 20, 147, 1),
    "deepskyblue": (0, 191, 255, 1), "dimgray": (105, 105, 105, 1),
    "dimgrey": (105, 105, 105, 1), "dodgerblue": (30, 144, 255, 1),
    "firebrick": (178, 34, 34, 1), "floralwhite": (255, 250, 240, 1),
    "forestgreen": (34, 139, 34, 1), "fuchsia": (255, 0, 255, 1),
    "gainsboro": (220, 220, 220, 1), "ghostwhite": (248, 248, 255, 1),
    "gold": (255, 215, 0, 1), "goldenrod": (218, 165, 32, 1),
    "gray": (128, 128, 128, 1), "green": (0, 128, 0, 1),
    "greenyellow": (173, 255, 47, 1), "grey": (128, 128, 128, 1),
    "honeydew": (240, 255, 240, 1), "hotpink": (255, 105, 180, 1),
    "indianred": (205, 92, 92, 1), "indigo": (75, 0, 130, 1),
    "ivory": (255, 255, 240, 1), "khaki": (240, 230, 140, 1),
    "lavender": (230, 230, 250, 1), "lavenderblush": (255, 240, 245, 1),
    "lawngreen": (124, 252, 0, 1), "lemonchiffon": (255, 250, 205, 1),
    "lightblue": (173, 216, 230, 1), "lightcoral": (240, 128, 128, 1),
    "lightcyan": (224, 255, 255, 1), "lightgoldenrodyellow": (250, 250, 210, 1),
    "lightgray": (211, 211, 211, 1), "lightgreen": (144, 238, 144, 1),
    "lightgrey": (211, 211, 211, 1), "lightpink": (255, 182, 193, 1),
    "lightsalmon": (255, 160, 122, 1), "lightseagreen": (32, 178, 170, 1),
    "lightskyblue": (135, 206, 250, 1), "lightslategray": (119, 136, 153, 1),
    "lightslategrey": (119, 136, 153, 1), "lightsteelblue": (176, 196, 222, 1),
    "lightyellow": (255, 255, 224, 1), "lime": (0, 255, 0, 1),
    "limegreen": (50, 205, 50, 1), "linen": (250, 240, 230, 1),
    "magenta": (255, 0, 255, 1), "maroon": (128, 0, 0, 1),
    "mediumaquamarine": (102, 205, 170, 1), "mediumblue": (0, 0, 205, 1),
    "mediumorchid": (186, 85, 211, 1), "mediumpurple": (147, 112, 219, 1),
    "mediumseagreen": (60, 179, 113, 1), "mediumslateblue": (123, 104, 238, 1),
    "mediumspringgreen": (0, 250, 154, 1), "mediumturquoise": (72, 209, 204, 1),
    "mediumvioletred": (199, 21, 133, 1), "midnightblue": (25, 25, 112, 1),
    "mintcream": (245, 255, 250, 1), "mistyrose": (255, 228, 225, 1),
    "moccasin": (255, 228, 181, 1), "navajowhite": (255, 222, 173, 1),
    "navy": (0, 0, 128, 1), "oldlace": (253, 245, 230, 1),
    "olive": (128, 128, 0, 1), "olivedrab": (107, 142, 35, 1),
    "orange": (255, 165, 0, 1), "orangered": (255, 69, 0, 1),
    "orchid": (218, 112, 214, 1), "palegoldenrod": (238, 232, 170, 1),
    "palegreen": (152, 251, 152, 1), "paleturquoise": (175, 238, 238, 1),
    "palevioletred": (219, 112, 147, 1), "papayawhip": (255, 239, 213, 1),
    "peachpuff": (255, 218, 185, 1), "peru": (205, 133, 63, 1),
    "pink": (255, 192, 203, 1), "plum": (221, 160, 221, 1),
    "powderblue": (176, 224, 230, 1), "purple": (128, 0, 128, 1),
    "rebeccapurple": (102, 51, 153, 1),
    "red": (255, 0, 0, 1), "rosybrown": (188, 143, 143, 1),
    "royalblue": (65, 105, 225, 1), "saddlebrown": (139, 69, 19, 1),
    "salmon": (250, 128, 114, 1), "sandybrown": (244, 164, 96, 1),
    "seagreen": (46, 139, 87, 1), "seashell": (255, 245, 238, 1),
    "sienna": (160, 82, 45, 1), "silver": (192, 192, 192, 1),
    "skyblue": (135, 206, 235, 1), "slateblue": (106, 90, 205, 1),
    "slategray": (112, 128, 144, 1), "slategrey": (112, 128, 144, 1),
    "snow": (255, 250, 250, 1), "springgreen": (0, 255, 127, 1),
    "steelblue": (70, 130, 180, 1), "tan": (210, 180, 140, 1),
    "teal": (0, 128, 128, 1), "thistle": (216, 191, 216, 1),
    "tomato": (255, 99, 71, 1), "turquoise": (64, 224, 208, 1),
    "violet": (238, 130, 238, 1), "wheat": (245, 222, 179, 1),
    "white": (255, 255, 255, 1), "whitesmoke": (245, 245, 245, 1),
    "yellow": (255, 255, 0, 1), "yellowgreen": (154, 205, 50, 1)]

// Clamp to integer 0 .. 255.
private func clamp_css_byte(_ i: Int) -> Int {
    return i < 0 ? 0 : i > 255 ? 255 : i
}

// Clamp to float 0.0 .. 1.0.
private func clamp_css_float(_ f: Double) -> Double {
    return f < 0 ? 0 : f > 1 ? 1 : f
}

// int or percentage.
private func parse_css_int(_ str: String) -> Int {
    if str.hasSuffix("%") {
        let digits = str.replacingOccurrences(of: "%", with: "")
        let value = (Double(digits) ?? 0) / 100 * 255
        return clamp_css_byte(Int(value))
    }

    return clamp_css_byte(Int(str) ?? 0)
}

// float or percentage.
private func parse_css_float(_ str: String) -> Double {
    if str.hasSuffix("%") {
        let digits = str.replacingOccurrences(of: "%", with: "")
        return clamp_css_float((Double(digits) ?? 0) / 100)
    }

    return clamp_css_float(Double(str) ?? 0)
}

private func css_hue_to_rgb(_ m1: Double, _ m2: Double, _ h: Double) -> Double {
    var h = h

    if h < 0 { h += 1 } else if h > 1 { h -= 1 }

    if h * 6 < 1 { return m1 + (m2 - m1) * h * 6 }
    if h * 2 < 1 { return m2 }
    if h * 3 < 2 { return m1 + (m2 - m1) * (2/3 - h) * 6 }
    return m1
}

func parseCSSColor(_ css_str: String) -> CSSColor? {
    // Remove all whitespace, not compliant, but should just be more accepting.
    var str = css_str.replacingOccurrences(of: " ", with: "").lowercased()

    // Color keywords (and transparent) lookup.
    if let match = kCSSColorTable[str] {
        return match;  // dup.
    }

    // #abc and #abc123 syntax.
    if str.first == "#" {
        if str.count == 4 {
            str.remove(at: str.startIndex) // TODO(deanm): Stricter parsing.
            guard let iv = Int(str, radix: 16) else { return nil }
            if !(iv >= 0 && iv <= 0xfff) { return nil } // Covers NaN.
            return (
                ((iv & 0xf00) >> 4) | ((iv & 0xf00) >> 8),
                (iv & 0xf0) | ((iv & 0xf0) >> 4),
                (iv & 0xf) | ((iv & 0xf) << 4),
                1
            )
        } else if str.count == 7 {
            str.remove(at: str.startIndex) // TODO(deanm): Stricter parsing.
            guard let iv = Int(str, radix: 16) else { return nil }
            if !(iv >= 0 && iv <= 0xffffff) { return nil } // Covers NaN.
            return (
                (iv & 0xff0000) >> 16,
                (iv & 0xff00) >> 8,
                iv & 0xff,
                1
            )
        } else if str.count == 9 {
            str.remove(at: str.startIndex)
            guard let iv = Int(str, radix: 16) else { return nil }
            if !(iv >= 0 && iv <= 0xffffffff) { return nil } // Covers NaN.
            return (
                (iv & 0xff000000) >> 24,
                (iv & 0xff0000) >> 16,
                (iv & 0xff00) >> 8,
                Double((iv & 0xff) / 255)
            )
        }

        return nil
    }

    let op = str.index(of: "(")
    let ep = str.index(of: ")")

    if let op = op, ep == str.index(before: str.endIndex), let ep = ep {
        let fname = String(str[..<op])

        let range: Range<String.Index> = str.index(after: op)..<ep
        var params: [String] = String(str[range]).components(separatedBy: ",")

        var alpha: Double = 1;  // To allow case fallthrough.

        // swiftlint:disable fallthrough
        switch fname {
        case "rgba":
            if params.count != 4 { return nil }
            alpha = parse_css_float(params.popLast()!)
            fallthrough
        case "rgb":
            if params.count != 3 { return nil }
            return (
                parse_css_int(params[0]),
                parse_css_int(params[1]),
                parse_css_int(params[2]),
                alpha
            )
        case "hsla":
            if params.count != 4 { return nil }
            alpha = parse_css_float(params.popLast()!)
            fallthrough
        case "hsl":
            if params.count != 3 { return nil }
            let hue = Double(params[0]) ?? 0
            let h = (hue.remainder(dividingBy: 360) + 360).remainder(dividingBy: 360) / 360 // 0 .. 1
            // NOTE(deanm): According to the CSS spec s/l should only be
            // percentages, but we don't bother and let float or percentage.
            let s = parse_css_float(params[1])
            let l = parse_css_float(params[2])
            let m2 = l <= 0.5 ? l * (s + 1) : l + s - l * s
            let m1 = l * 2 - m2
            return (
                clamp_css_byte(Int(css_hue_to_rgb(m1, m2, h+1/3) * 255.0)),
                clamp_css_byte(Int(css_hue_to_rgb(m1, m2, h) * 255.0)),
                clamp_css_byte(Int(css_hue_to_rgb(m1, m2, h-1/3) * 255.0)),
                alpha
            )
        default:
            return nil
        }
        // swiftlint:enable fallthrough
    }

    return nil
}

private func colorComponent(for int: Int) -> CGFloat {
    return CGFloat(int) / 255.0
}

#if os(iOS)
    import UIKit

    extension UIColor {
        static func parse(css: String) -> UIColor? {
            guard let color = parseCSSColor(css) else { return nil }

            return UIColor(
                red: colorComponent(for: color.r),
                green: colorComponent(for: color.g),
                blue: colorComponent(for: color.b),
                alpha: CGFloat(color.a)
            )
        }
    }
#elseif os(OSX)
    import AppKit

    extension NSColor {
        static func parse(css: String) -> NSColor? {
            guard let color = parseCSSColor(css) else { return nil }

            return NSColor(
                red: colorComponent(for: color.r),
                green: colorComponent(for: color.g),
                blue: colorComponent(for: color.b),
                alpha: CGFloat(color.a)
            )
        }
    }
#endif
