//
//  Book.swift
//  oosxl
//

import Foundation
import cxx_oosxl

public enum CalcMode: Int32 {
    case auto = 0
    case manual = 1
    case autoNoTable = 2
}

public enum SheetType: Int32 {
    case unknown
    case worksheet
    case chartsheet
    case dialogsheet
}

public enum SheetState: Int32 {
    case visible
    case hidden
    case veryHidden
}

public class Workbook {
    let handle: WorkbookHandle

    init(withHandle handle: WorkbookHandle) {
        self.handle = handle
    }

    public enum DateMode: Int {
        /// The lower date limit is January 1, 1900
        case date1900 = 0
        /// The lower date limit is January 1, 1904
        case date1904 = 1
    }

    public var dateMode: DateMode {
        get {
            return DateMode(rawValue: Int(wb_is_date1904(self.handle)))!
        }
        set {
            wb_set_date1904(self.handle, Int32(newValue.rawValue))
        }
    }

    public var a1RefMode: Bool {
        get {
            return wb_is_a1_ref_mode(self.handle) != 0
        }
        set {
            wb_set_a1_ref_mode(self.handle, newValue ? 1 : 0)
        }
    }

    public var calcMode: CalcMode {
        get {
            return CalcMode(rawValue: wb_get_calc_mode(self.handle))!
        }
        set {
            wb_set_calc_mode(self.handle, newValue.rawValue)
        }
    }

    public var sheets: [Worksheet] {
        let count = Int(wb_sheet_count(self.handle))
        var worksheets: [Worksheet] = []
        for index in 0..<count {
            if let sheetHandle = wb_get_sheet(self.handle, Int32(index)) {
                worksheets.append(Worksheet(withHandle: sheetHandle, workbook: self))
            }
        }
        return worksheets
    }

    public func addSheet() -> Worksheet? {
        let handle = wb_add_worksheet(self.handle, nil)
        return handle.map { Worksheet(withHandle: $0, workbook: self) }
    }

    public func addSheet(sheetname: String) -> Worksheet? {
        if sheetname == "" { return addSheet() }
        let handle = sheetname.withCString { cString in
            return wb_add_worksheet(self.handle, cString)
        }
        return handle.map { Worksheet(withHandle: $0, workbook: self) }
    }

    public func insertSheet(tab: Int, sheetname: String? = nil) -> Worksheet? {
        let handle =
            sheetname?.withCString { name in
                wb_insert_worksheet(self.handle, Int32(tab), name)
            } ?? wb_insert_worksheet(self.handle, Int32(tab), nil)

        return handle.map { Worksheet(withHandle: $0, workbook: self) }
    }

    public func deleteSheet(byIndex tab: Int) {
        wb_delete_sheet(self.handle, Int32(tab))
    }

    public func numSheets() -> Int32 {
        return Int32(wb_sheet_count(self.handle))
    }

    public func sheetType(index: Int) -> SheetType {
        let value = wb_sheet_type(self.handle, Int32(index))
        return SheetType(rawValue: value) ?? .unknown
    }

    public func sheetState(index: Int) -> SheetState {
        let value = wb_get_sheet_state(self.handle, Int32(index))
        return SheetState(rawValue: value) ?? .visible
    }

    public func setSheetState(index: Int, state: SheetState) {
        wb_set_sheet_state(self.handle, Int32(index), state.rawValue)
    }

    public func sheetName(index: Int) -> String? {
        guard let name = wb_sheet_name(self.handle, Int32(index))
        else { return nil }
        return String(cString: name)
    }

    public func sheet(byIndex index: Int) -> Worksheet? {
        let handle: WorksheetHandle? = wb_get_sheet(self.handle, Int32(index))
        return handle.map { Worksheet(withHandle: $0, workbook: self) }
    }

    public var activeSheet: Int {
        get {
            return Int(wb_get_active_sheet(self.handle))
        }
        set {
            wb_set_active_sheet(self.handle, Int32(newValue))
        }
    }

    public func renameSheet(tab: Int, newname: String) {
        newname.withCString { name in
            wb_rename_sheet(self.handle, Int32(tab), name)
        }
    }

    public func shiftSheet(from srcIndex: Int, to dstIndex: Int) {
        wb_shift_sheet(self.handle, Int32(srcIndex), Int32(dstIndex))
    }

    public func setDefinedName(definedName: String, expression: String, localSheet: String? = nil)
        -> Bool
    {
        let result = definedName.withCString { namePtr in
            expression.withCString { exprPtr in
                if let localSheet = localSheet {
                    return localSheet.withCString { localPtr in
                        wb_set_defined_name(self.handle, namePtr, exprPtr, localPtr)
                    }
                } else {
                    return wb_set_defined_name(self.handle, namePtr, exprPtr, nil)
                }
            }
        }

        return result != 0
    }

    public var definedNameNum: Int {
        return Int(wb_defined_name_num(self.handle))
    }

    func getDefinedName(index: Int) -> (name: String, localSheet: String, expression: String)? {
        var isLocal: Int32 = 0
        guard let namePtr = wb_get_defined_name(self.handle, Int32(index), &isLocal) else {
            return nil
        }
        let name = String(cString: namePtr)

        guard let exprPtr = wb_get_defined_name_expr(self.handle, Int32(index)) else {
            return nil
        }
        let expression = String(cString: exprPtr)

        var localSheet = ""
        if isLocal != 0 {
            if let localPtr = wb_get_local_name(self.handle, Int32(index)) {
                localSheet = String(cString: localPtr)
            }
        }

        return (name: name, localSheet: localSheet, expression: expression)
    }

    public func deleteDefinedName(index: Int) {
        wb_delete_defined_name(self.handle, Int32(index))
    }

    public func getNamedStyle(_ name: String) -> Style? {
        let handle = name.withCString { ptr in
            return wb_get_named_style(self.handle, ptr)
        }

        return handle.map { Style(withHandle: $0) }
    }

    public func addCustomStyle(_ name: String, _ format: Style) -> Style? {
        let handle = name.withCString { ptr in
            wb_add_custom_style(self.handle, ptr, format.handle)
        }

        return handle.map { Style(withHandle: $0) }
    }

    public func modifyNamedStyle(_ name: String, _ format: Style) -> Bool {
        let result = name.withCString { ptr in
            wb_modify_named_style(self.handle, ptr, format.handle)
        }

        return result != 0
    }

    public var normalStyle: Style? {
        guard let handle = wb_make_normal_style(self.handle) else { return nil }
        return Style(withHandle: handle)
    }

    public func makeRichtext() -> Richtext {
        let handle = wb_make_richtext(self.handle)
        return Richtext(withHandle: handle!)
    }
}
