//
//  Utils.swift
//  oosxl
//

import Foundation
import cxx_oosxl

public enum oosxl {

    public static func createDocument(from filename: String? = nil) -> Document? {
        let handleMaybe = doc_create()
        guard let handle = handleMaybe else { return nil }

        return Document(with: handle)
    }

    public static func loadDocument(from filename: String) -> Document? {
        let handleMaybe = filename.withCString { cString in
            return doc_load(cString)
        }
        guard let handle = handleMaybe else { return nil }

        return Document(with: handle)
    }

    public static func version() -> String {
        let ver = get_version()!
        return String(cString: ver)
    }

    public static var runmsg: String {
        guard let cmsg = run_msg() else { return "" }
        return String(cString: cmsg)
    }

    static func path(for filename: String, in relativePath: String? = nil) -> URL {
        let fileManager = FileManager.default

        let homeDirectory = fileManager.homeDirectoryForCurrentUser

        var fileURL = homeDirectory
        if let relativePath = relativePath {
            fileURL = fileURL.appendingPathComponent(relativePath)
        }
        return fileURL.appendingPathComponent(filename)
    }

    public static func a1ToNum(_ a1: String) -> (row: Int32, col: Int32)? {
        var row: Int32 = 0
        var col: Int32 = 0

        let result = a1.withCString { cstring -> Int32 in
            return Int32(a1_to_row_col(cstring, &row, &col))
        }

        return (result != 0) ? (row, col) : nil
    }

    public static func numToA1(row: Int32, col: Int32) -> String? {
        var buffer = [CChar](repeating: 0, count: 32)

        let success = buffer.withUnsafeMutableBufferPointer { bufferPtr in
            guard let baseAddress = bufferPtr.baseAddress else { return false }
            let cResult = row_col_to_a1(row, col, baseAddress)
            return cResult != 0
        }

        guard success else { return nil }

        return buffer.withUnsafeBufferPointer { bufferPtr in
            guard let baseAddress = bufferPtr.baseAddress else { return nil }
            return String(validatingCString: baseAddress)
        }
    }

    public static func decodeRGB(rgb: Int32) -> (red: Int32, green: Int32, blue: Int32) {
        var red: Int32 = 0
        var green: Int32 = 0
        var blue: Int32 = 0
        decode_rgb(CLong(rgb), &red, &green, &blue)
        return (red, green, blue)
    }

    public static func encodeRGB(red: Int32, green: Int32, blue: Int32) -> Int32 {
        return Int32(encode_rgb(red, green, blue))
    }

    public static func decodeARGB(argb: Int32) -> (
        red: Int32, green: Int32, blue: Int32, alpha: Int32
    ) {
        var red: Int32 = 0
        var green: Int32 = 0
        var blue: Int32 = 0
        var alpha: Int32 = 0
        decode_argb(CLong(argb), &red, &green, &blue, &alpha)
        return (red, green, blue, alpha)
    }

    public static func encodeARGB(red: Int32, green: Int32, blue: Int32, alpha: Int32) -> Int32 {
        return Int32(encode_argb(red, green, blue, alpha))
    }

    public static func decodeDate(doubleValue: Double, isDate1904: Bool = false)
        -> DateComponents
    {
        var year: Int32 = 0
        var month: Int32 = 0
        var day: Int32 = 0

        if isDate1904 {
            decode_date1904(doubleValue, &year, &month, &day)
        } else {
            decode_date(doubleValue, &year, &month, &day)
        }

        return DateComponents(
            calendar: nil, timeZone: nil, era: nil, year: Int(year), month: Int(month),
            day: Int(day))
    }

    public static func decodeDatetime(doubleValue: Double, isDate1904: Bool = false)
        -> DateComponents
    {
        var year: Int32 = 0
        var month: Int32 = 0
        var day: Int32 = 0
        var hour: Int32 = 0
        var min: Int32 = 0
        var sec: Int32 = 0
        var msec: Int32 = 0

        if isDate1904 {
            decode_datetime1904(doubleValue, &year, &month, &day, &hour, &min, &sec, &msec)
        } else {
            decode_datetime(doubleValue, &year, &month, &day, &hour, &min, &sec, &msec)
        }

        return DateComponents(
            calendar: nil, timeZone: nil, era: nil, year: Int(year), month: Int(month),
            day: Int(day), hour: Int(hour), minute: Int(min), second: Int(sec),
            nanosecond: Int(msec) * 1_000_000)
    }

    public static func encodeDate(year: Int32, month: Int32, day: Int32) -> Double {
        return encode_date(year, month, day)
    }

    public static func encodeTime(hour: Int32, minute: Int32, second: Int32, msec: Int32) -> Double
    {
        return encode_time(hour, minute, second, msec)
    }

    public static func encodeDatetime(
        year: Int32, month: Int32, day: Int32, hour: Int32, minute: Int32, second: Int32,
        msec: Int32 = 0
    ) -> Double {
        return encode_datetime(year, month, day, hour, minute, second, msec)
    }

    private static let utcCalendar: Calendar = {
        var calendar = Calendar(identifier: .gregorian)
        calendar.timeZone = TimeZone(secondsFromGMT: 0)!
        return calendar
    }()

    public static func encodeDatetime(date: Date) -> Double {
        let units: Set<Calendar.Component> = [
            .year, .day, .month, .hour, .minute, .second, .nanosecond,
        ]

        let components = utcCalendar.dateComponents(units, from: date)

        return encodeDatetime(
            year: Int32(components.year!),
            month: Int32(components.month!),
            day: Int32(components.day!),
            hour: Int32(components.hour!),
            minute: Int32(components.minute!),
            second: Int32(components.second!),
            msec: Int32(components.nanosecond! / 1_000_000))
    }
}
