/*
 * Copyright (c) Huawei Technologies Co., Ltd. 2025. All rights reserved.
 * This source file is part of the Cangjie project, licensed under Apache-2.0
 * with Runtime Library Exception.
 *
 * See https://cangjie-lang.cn/pages/LICENSE for license information.
 */

/**
 * @file
 *
 * This file defines FormatStyle related classes.
 */

package std.time

import std.collection.ArrayList

public class DateTimeFormat {
    public static const RFC1123: String = "www, dd MMM yyyy HH:mm:ss z"
    public static const RFC3339: String = "yyyy-MM-ddTHH:mm:ssOOOO"
    public static const RFC822: String = "ww dd MMM yy HH:mm:ss z"
    public static const RFC850: String = "wwww, dd-MMM-yy HH:mm:ss z"
    let data = ArrayList<FormatType>()
    let rawString: String

    init(formatString: String) {
        rawString = formatString
        let layout = rawString.toRuneArray()
        let size = layout.size
        var index = 0
        var formatType: FormatType
        while (index < size) {
            (index, formatType) = parseFormat(layout, index)
            formatType.checkLength()
            data.add(formatType)
        }
        data.add(FormatType.Termination)
    }

    @Deprecated["Use member function `init(formatString: String)` instead."]
    public static func of(format: String): DateTimeFormat {
        return DateTimeFormat(format)
    }

    @Deprecated["The prop is deprecated, no substitutions."]
    public prop format: String {
        get() {
            rawString
        }
    }
}

enum Text {
    TxtMonth | TxtDayOfWeek | TxtEra
}

enum Numeric {
    NumDay | NumDayOfYear | NumWeekOfYear | NumHour12 | NumHour24 | NumMinute | NumSecond
}

enum FormatType {
    | FormatYear(Int64) // y
    | FormatISOYear(Int64) // Y
    | FormatMonth(Int64) // M
    | FormatDayOfMonth(Int64) // d
    | FormatHourIn24(Int64) // H
    | FormatHourIn12(Int64) // h
    | FormatMinute(Int64) // m
    | FormatSecond(Int64) // s
    | FormatSecondFraction(Int64) // S
    | FormatOffset(Int64) // O
    | FormatZoneInfo(Int64) // z
    | FormatGMTOffset(Int64) // Z
    | FormatDayOfYear(Int64) // D
    | FormatAmPm(Int64) // a
    | FormatDayOfWeek(Int64) // w
    | FormatISOWeek(Int64) // W
    | FormatEra(Int64) // G
    | Separator(Rune)
    | Termination

    /**
     * Check whether the length is correct.
     *
     * @throws IllegalArgumentException if the number of character is invalid
     */
    func checkLength(): Unit {
        match (this) {
            case FormatYear(length) => checkFormatLength(length, 9, r'y')
            case FormatISOYear(length) => checkFormatLength(length, 9, r'Y')
            case FormatMonth(length) => checkFormatLength(length, 4, r'M')
            case FormatDayOfMonth(length) => checkFormatLength(length, 2, r'd')
            case FormatHourIn24(length) => checkFormatLength(length, 2, r'H')
            case FormatHourIn12(length) => checkFormatLength(length, 2, r'h')
            case FormatMinute(length) => checkFormatLength(length, 2, r'm')
            case FormatSecond(length) => checkFormatLength(length, 2, r's')
            case FormatSecondFraction(length) => checkFormatLength(length, 3, r'S')
            case FormatOffset(length) => checkFormatLength(length, 4, r'O')
            case FormatZoneInfo(length) => checkFormatLength(length, 4, r'z')
            case FormatGMTOffset(length) => checkFormatLength(length, 4, r'Z')
            case FormatDayOfYear(length) => checkFormatLength(length, 2, r'D')
            case FormatAmPm(length) => checkFormatLength(length, 1, r'a')
            case FormatDayOfWeek(length) => checkFormatLength(length, 4, r'w')
            case FormatISOWeek(length) => checkFormatLength(length, 2, r'W')
            case FormatEra(length) => checkFormatLength(length, 3, r'G')
            case _ => ()
        }
    }

    func checkFormatLength(length: Int64, maxLen: Int64, r: Rune): Unit {
        if (length > maxLen) {
            throw IllegalArgumentException("The number of character \"${r}\" can not exceed ${maxLen}.")
        }
    }
}

func parseFormat(formatString: String): ArrayList<FormatType> {
    let data = ArrayList<FormatType>()
    let layout = formatString.toRuneArray()
    let size = layout.size
    var index = 0
    var formatType: FormatType
    while (index < size) {
        (index, formatType) = parseFormat(layout, index)
        formatType.checkLength()
        data.add(formatType)
    }
    data.add(FormatType.Termination)
    return data
}

/**
 * Return end index and format type of a DateTime format.
 *
 * @param layout the format of datetime.
 * @param index the starting point of format parsing
 * @return an ending point and the parsed type of format.
 */
func parseFormat(layout: Array<Rune>, index: Int64): (Int64, FormatType) {
    let size = layout.size
    for (idx in index..size) {
        let end = findFormat(idx, layout)
        let length = end - idx
        match (layout[idx]) {
            case r'y' => return (end, FormatYear(length))
            case r'Y' => return (end, FormatISOYear(length))
            case r'M' => return (end, FormatMonth(length))
            case r'd' => return (end, FormatDayOfMonth(length))
            case r'H' => return (end, FormatHourIn24(length))
            case r'h' => return (end, FormatHourIn12(length))
            case r'm' => return (end, FormatMinute(length))
            case r's' => return (end, FormatSecond(length))
            case r'S' => return (end, FormatSecondFraction(length))
            case r'O' => return (end, FormatOffset(length))
            case r'z' => return (end, FormatZoneInfo(length))
            case r'Z' => return (end, FormatGMTOffset(length))
            case r'a' => return (end, FormatAmPm(length))
            case r'D' => return (end, FormatDayOfYear(length))
            case r'w' => return (end, FormatDayOfWeek(length))
            case r'W' => return (end, FormatISOWeek(length))
            case r'G' => return (end, FormatEra(length))
            case _ => return (idx + 1, Separator(layout[idx]))
        }
    }
    return (index, Termination)
}

func findFormat(start: Int64, layout: Array<Rune>) {
    var pos = start + 1
    while (pos < layout.size && layout[pos] == layout[start]) {
        pos++
    }
    return pos
}