local GruvboxDark0Hard = 0x1d2021ff
local GruvboxDark0 = 0x282828ff
local GruvboxDark0Soft = 0x32302fff
local GruvboxDark1 = 0x3c3836ff
local GruvboxDark2 = 0x504945ff
local GruvboxDark3 = 0x665c54ff
local GruvboxDark4 = 0x7c6f64ff
local GruvboxGray245 = 0x928374ff
local GruvboxGray244 = 0x928374ff
local GruvboxLight0Hard = 0xf9f5d7ff
local GruvboxLight0 = 0xfbf1c7ff
local GruvboxLight0Soft = 0xf2e5bcff
local GruvboxLight1 = 0xebdbb2ff
local GruvboxLight2 = 0xd5c4a1ff
local GruvboxLight3 = 0xbdae93ff
local GruvboxLight4 = 0xa89984ff
local GruvboxBrightRed = 0xfb4934ff
local GruvboxBrightGreen = 0xb8bb26ff
local GruvboxBrightYellow = 0xfabd2fff
local GruvboxBrightBlue = 0x83a598ff
local GruvboxBrightPurple = 0xd3869bff
local GruvboxBrightAqua = 0x8ec07cff
local GruvboxBrightOrange = 0xfe8019ff
local GruvboxNeutralRed = 0xcc241dff
local GruvboxNeutralGreen = 0x98971aff
local GruvboxNeutralYellow = 0xd79921ff
local GruvboxNeutralBlue = 0x458588ff
local GruvboxNeutralPurple = 0xb16286ff
local GruvboxNeutralAqua = 0x689d6aff
local GruvboxNeutralOrange = 0xd65d0eff
local GruvboxFadedRed = 0x9d0006ff
local GruvboxFadedGreen = 0x79740eff
local GruvboxFadedYellow = 0xb57614ff
local GruvboxFadedBlue = 0x076678ff
local GruvboxFadedPurple = 0x8f3f71ff
local GruvboxFadedAqua = 0x427b58ff
local GruvboxFadedOrange = 0xaf3a03ff
Color = {}
Color.Text = GruvboxDark0Hard
Color.LoadTextHighlight = 0x0000000F
Color.Background = GruvboxLight0Hard
Color.InactiveWindow = 0x0000000F
Color.TextLineNumbers = GruvboxDark4
Color.LineHighlight = GruvboxLight0Soft
Color.MainCaret = GruvboxDark0Hard
Color.SubCaret = GruvboxGray245
Color.Selection = GruvboxLight1
Color.WhitespaceDuringSelection = GruvboxLight4
Color.MouseUnderline = GruvboxDark0Hard
Color.CaretUnderline = GruvboxGray245
Color.FuzzySearchLineHighlight = GruvboxDark0
Color.ScrollbarBackground = GruvboxLight2
Color.ScrollbarScroller = GruvboxLight1
Color.ScrollbarScrollerSelected = GruvboxLight0Hard
Color.TitleBarText = GruvboxDark2
Color.TitleBarBackground = GruvboxLight1
Color.TitleBarActiveBackground = 0xfefefefe
Color.TitleBarSelection = GruvboxLight3
Color.ResizerBackground = GruvboxLight0Hard
Color.ResizerOutline = GruvboxLight3
Style = {}
Style.WaitForEvents = 1
Style.DrawLineNumbers = 1
Style.DrawScrollbar = 1
Style.IndentSize = 4
Style.FontSize = 15
Style.Font = GetExeDir().."/CascadiaMono.ttf"
Style.VCVarsall = "C:/Program Files/Microsoft Visual Studio/2022/Community/VC/Auxiliary/Build/vcvars64.bat"
Style.TrimWhitespaceOnSave = true
Style.ClangFormatOnSave = false
Style.StyleUndoMergeTimeout = 0.3

INTERNET_BROWSER = 'C:/Program Files/BraveSoftware/Brave-Browser/Application/brave.exe'
OS_WINDOWS = 0
OS_UNIX = 1

SDLK_CTRL = 1073742048
SDLK_PAGE_DOWN = 1073741902
SDLK_PAGE_UP = 1073741899
SDLK_DOWN = 1073741905 -- 0x40000051
SDLK_UP = 1073741906 -- 0x40000052u
SDLK_RIGHT = 1073741903
SDLK_LEFT = 1073741904
SDLK_Q = 113
SDLK_BACKSLASH = 0x5c
SDLK_RETURN = 13

SDLK_EQUALS = 0x0000003d
SDLK_MINUS = 0x0000002d

SDLK_F1  = 0x4000003a
SDLK_F2  = 0x4000003b
SDLK_F3  = 0x4000003c
SDLK_F4  = 0x4000003d
SDLK_F5  = 0x4000003e
SDLK_F6  = 0x4000003f
SDLK_F7  = 0x40000040
SDLK_F8  = 0x40000041
SDLK_F9  = 0x40000042
SDLK_F10 = 0x40000043
SDLK_F11 = 0x40000044
SDLK_F12 = 0x40000045

SDLK_A = 0x00000061
SDLK_B = 0x00000062
SDLK_C = 0x00000063
SDLK_D = 0x00000064
SDLK_E = 0x00000065
SDLK_F = 0x00000066
SDLK_G = 0x00000067
SDLK_H = 0x00000068
SDLK_I = 0x00000069
SDLK_J = 0x0000006a
SDLK_K = 0x0000006b
SDLK_L = 0x0000006c
SDLK_M = 0x0000006d
SDLK_N = 0x0000006e
SDLK_O = 0x0000006f
SDLK_P = 0x00000070
SDLK_Q = 0x00000071
SDLK_R = 0x00000072
SDLK_S = 0x00000073
SDLK_T = 0x00000074
SDLK_U = 0x00000075
SDLK_V = 0x00000076
SDLK_W = 0x00000077
SDLK_X = 0x00000078
SDLK_Y = 0x00000079
SDLK_Z = 0x0000007a

VERTICAL = 1
HORIZONTAL = 2

function IsAlpha(a)
    if a == nil then
        return false
    end
    local x = string.byte(a)
    local result = (x >= string.byte('a') and x <= string.byte('z')) or (x >= string.byte('A') and x <= string.byte('Z'))
    return result
end

function SkipLineAndColumn(s)
    local line, col = "-1", "-1"

    function parse_line_and_column(line_and_col, delimiter)
        ic, jc = line_and_col:find(delimiter)
        line = line_and_col:sub(1, ic - 1)
        col = line_and_col:sub(ic + 1)
        return line, col
    end

    do -- :line:column
        local i, j = s:find("^:%d+:%d+")
        if i then
            skip_pattern = s:sub(j + 1)
            line, col = parse_line_and_column(s:sub(2, j), ":")
            return line, col, skip_pattern
        end
    end

    do -- :line
        local i, j = s:find("^:%d+")
        if i then
            skip_pattern = s:sub(j + 1)
            line = s:sub(2, j)
            return line, col, skip_pattern
        end
    end

    do -- (line,column)
        local i, j = s:find("^%(%d+,%d+%)")
        if i then
            skip_pattern = s:sub(j + 1)
            line, col = parse_line_and_column(s:sub(2, j - 1), ",")
            return line, col, skip_pattern
        end
    end

    do -- (line)
        local i, j = s:find("^%(%d+%)")
        if i then
            skip_pattern = s:sub(j + 1)
            line = s:sub(2, j - 1)
            return line, col, skip_pattern
        end
    end

    return line, col, s
end

function SkipSlashes(s)
    found_slash = false
    while s:sub(1,1) == '/' or s:sub(1,1) == '\\' do
        s = s:sub(2)
        found_slash = true
    end
    return s, found_slash
end

function SkipDrive(s)
    local i, j = s:find("^%a:")
    if not i then
        return s, nil, true
    end

    local drive = s:sub(i, j)
    local new_s = s:sub(j + 1)

    new_s, found_slash = SkipSlashes(new_s)
    if not found_slash then
        return s, drive, false
    end

    return new_s, drive, true
end

function WindowsSkipPathCell(s)
    local i, j = s:find("^[%w_%.-% +]+")
    if not i then return s, nil, false end

    local word  = s:sub(i, j)
    local new_s = s:sub(j + 1)
    local new_s, found_slash = SkipSlashes(new_s)

    return new_s, word, found_slash
end

function WindowsSkipPath(s)
    local input_s = s
    local s, drive, ok = SkipDrive(s)
    if not ok then return s end

    local cells = {}
    if drive ~= nil then
        table.insert(cells, drive)
    end

    while true do
        s, word, slash_eaten = WindowsSkipPathCell(s)
        if word then cells[#cells + 1] = word end
        if not slash_eaten then break end
    end

    if #cells == 0 then return s end

    local skip_size = input_s:len() - s:len()
    local path = input_s:sub(1, skip_size)
    return s, path, drive, cells
end

function MatchWindowsPath(_s, meta)
    local s, file_path, drive = WindowsSkipPath(_s)

    if not file_path and FileExists(drive) then
        return {kind = "text", file_path = drive, line = line, col = col}
    end

    if not file_path then
        return nil
    end

    if not drive then
        local d = GetMainDir()
        file_path = d..'/'..file_path
    end
    local line, col, s = SkipLineAndColumn(s)

    local exists = FileExists(file_path) or BufferExists(file_path)
    if not exists then return nil end

    return {kind = "text", file_path = file_path, line = line, col = col}
end

function MatchUnixPath(s, meta)
    local i, j = s:find("^[%w_%.-% %/]+")
    local path = s:sub(i, j)
    local rest = s:sub(j + 1, -1)
    local line, col, s = SkipLineAndColumn(rest)
    Print(path, rest, line, col)

    return {kind = "text", file_path = path, line = line, col = col}
end

if OS_VALUE == OS_WINDOWS then
    MatchPath = MatchWindowsPath
else
    MatchPath = MatchUnixPath
end

function MatchGitCommit(s, meta)
    local i, j = string.find(s, "^commit ([a-zA-Z0-9]+)")
    if i then
        s = s:sub(8)
        local command = "git --no-pager show "..s
        return {kind = "exec", cmd = command, working_dir = GetMainDir()}
    end
    return nil
end

function MatchURL(s, meta)
    local i, j = string.find(s, "^https://")
    if i then
        return {kind = "exec_console", cmd = '"'..INTERNET_BROWSER..'" '..s, working_dir = GetMainDir()}
    end
    return nil
end

function MatchGotoBuild(s, meta)
    if meta ~= "goto_build" then
        return nil
    end

    local windows_path = IsAlpha(s:sub(1,1)) and s:sub(2,2) == ':'
    local windows_rela = s:sub(1,1) == '.'   and s:sub(2,2) == '\\'
    local unix_path    = s:sub(1,1) == '/'
    local unix_rela    = s:sub(1,1) == '.'   and s:sub(2,2) == '/'

    if not windows_path and not windows_rela and not unix_path and not windows_rela then
        return {kind = "skip"}
    end

    return nil
end

function MatchExec(s, meta)
    if s:match(".exe$") or s:match(".bat$") or s:match(".sh$") then
        return {kind = "exec_console", cmd = s, working_dir = GetMainDir()}
    end

    if s:match("^%$") then
        return {kind = "exec_console", cmd = s:sub(2, -1), working_dir = GetMainDir()}
    end

    return nil
end

BuiltinOnOpenMatchers = {
    MatchPath,
    MatchGitCommit,
    MatchURL,

    MatchGotoBuild,

    MatchExec,
}
OnOpenMatchers = BuiltinOnOpenMatchers

function OnOpen(path, meta)
    for i = #OnOpenMatchers,1,-1 do
        rule = OnOpenMatchers[i]
        result = rule(path, meta)
        if result then
            return result
        end
    end
    return nil
end

Coroutines = {}
function CoAdd(f)
    local i = #Coroutines + 1
    Coroutines[i] = coroutine.create(f)
    coroutine.resume(Coroutines[i])
    return Coroutines[i]
end

function OnUpdate(e)
    local new_co_list = {}
    for key, co in pairs(Coroutines) do
        local status = coroutine.status(co)
        if status ~= "dead" then
            coroutine.resume(co, e)
            new_co_list[#new_co_list + 1] = co
        end
    end
    Coroutines = new_co_list
end


function KeybindsBasic(e)
    if e.key == SDLK_F and e.ctrl == 1 and e.shift == 1 then
        C("git grep -n "..GetLoadWord().." :/")
        return true
    end
    return false
end

function BasicBuild()
    SaveAll()
    if OS_VALUE == OS_WINDOWS then
        return "build.bat"
    else
        return "./build.sh"
    end
end

FKey = {BasicBuild, "", "", "", "", "", "", "", "", "", "", ""}
FKeySDLK = {SDLK_F1, SDLK_F2, SDLK_F3, SDLK_F4, SDLK_F5, SDLK_F6, SDLK_F7, SDLK_F8, SDLK_F9, SDLK_F10, SDLK_F11, SDLK_F12}


function KeybindsFKeys(e)
    for i = #FKey,1,-1 do
        if FKey[i] ~= "" then
            if e.key == FKeySDLK[i] then
                local cmdline = FKey[i]
                if type(cmdline) == "function" then cmdline = FKey[i]() end
                Cmd { working_dir = GetWorkDir(), kind = "console", cmd = cmdline }
                return true
            end
        end
    end
    return false
end

BuiltinOnCommandCallbacks = {
    KeybindsBasic,
    KeybindsFKeys,
}
OnCommandCallbacks = BuiltinOnCommandCallbacks

function OnCommand(e)
    for i = #OnCommandCallbacks,1,-1 do
        on_command = OnCommandCallbacks[i]
        local result = on_command(e)
        if result then
            return true
        end
    end
    return false
end

function OnInit()
end

function OnSave(buffer_id)
    local dont_trim_lines_with_caret = false
    ConvertLineEndingsToLF(buffer_id, dont_trim_lines_with_caret)
    if Style.TrimWhitespaceOnSave then
        TrimTrailingWhitespace(buffer_id, dont_trim_lines_with_caret)
    end

    local name = GetBufferNameByID(buffer_id)
    if Style.ClangFormatOnSave and (name:match(".c$") or name:match(".h$") or name:match(".cpp$") or name:match(".hpp$")) then
        ApplyClangFormat(buffer_id)
    end
end

function IsCodeExclude(s, meta)
    if s:match("/.git$") or s:match("\\.git$") or
       s:match(".exe$") or
       s:match(".bin$") or
       s:match(".obj$") or
       s:match(".o$") or
       s:match(".lib$") or
       s:match(".ilk$") or
       s:match(".cache$") or
       s:match(".exp$") or
       s:match(".pdb$") or
       s:match("/external/") or s:match("\\external\\")
    then
        return false
    end
    return true
end

BuiltinIsCodeMatchers = {
    IsCodeExclude,
}
IsCodeMatchers = BuiltinIsCodeMatchers

function IsCode(path, meta)
    for i = #IsCodeMatchers,1,-1 do
        rule = IsCodeMatchers[i]
        result = rule(path, meta)
        if result then
            return result
        end
    end
    return false
end