------------------------------------------------------------------------------------------------------------
local ffi = require( "ffi" )

local kernel32 	    = ffi.load( "kernel32.dll" )
local user32 	    = ffi.load( "user32.dll" )
local comdlg32      = ffi.load( "Comdlg32.dll" )
local gdi32         = ffi.load( "gdi32.dll" )
local shell32       = ffi.load( "shell32.dll" )
local ole32         = ffi.load( "ole32.dll" )
local shlwapi       = ffi.load( "shlwapi.dll" )

local Colors = {
	-- Color constants.
	rgbRed   =  0x000000FF,
	rgbGreen =  0x0000FF00,
	rgbBlue  =  0x00FF0000,
	rgbBlack =  0x00000000,
	rgbWhite =  0x00FFFFFF,
}

ffi.cdef[[
    void Sleep(uint32_t ms);

    typedef struct enumData {
        uint64_t        lparam[1];
        const char *    title;
    } enumData;
]]

require("ffi.windows")

local function AssocQ( outstr, bsize )
    local ftype = ffi.cast("char *", ffi.string("http"))
    local verb = ffi.cast("char *", ffi.string("open"))
    local hr = shlwapi.AssocQueryStringA(user32.ASSOCF_NONE, user32.ASSOCSTR_EXECUTABLE, ftype, verb, outstr, bsize)
    return outstr, bsize
end

-- -----------------------------------------------------------------------------------

local function getDefaultBrowser()
    local bsize = ffi.new("uint32_t[1]")
    AssocQ( nil, bsize)
    local outstr = ffi.new("char[?]", bsize[0])
    local hr = AssocQ( outstr, bsize)
    return outstr, bsize
end

-- -----------------------------------------------------------------------------------
-- To be added to UTILS
local function execute(commandLine, currentDirectory)
    local si = ffi.new("STARTUPINFOA[1]")
    si[0].cb = ffi.sizeof(si[0])
    -- si[0].dwFlags = ffi.C.STARTF_USESTDHANDLES
    local pi = ffi.new("PROCESS_INFORMATION[1]")
    local hproc = ffi.C.CreateProcessA(nil, commandLine, nil, nil, 1, 0, nil, currentDirectory, si, pi) ~= 0
    -- Short delay to make sure the process has at least begun.
    ffi.C.Sleep(200)
    return pi[0].dwProcessId, pi[0].dwThreadId, pi
end   

-- -----------------------------------------------------------------------------------
-- To be added to UTILS
local function shellexecute(hwnd, commandline, directory)

    local cmdstr = ffi.new("char[?]", #commandline + 1)
    ffi.copy(cmdstr, ffi.string(commandline))
    
    local si = ffi.new("SHELLEXECUTEINFOA[1]")
    si[0].cbSize = ffi.sizeof(si[0])
    si[0].fMask = user32.SEE_MASK_NOCLOSEPROCESS
    si[0].hwnd = 0
    si[0].lpVerb = nil
    si[0].lpFile = ffi.cast("char *", cmdstr)
    si[0].lpParameters = nil
    si[0].lpDirectory = nil
    -- si[0].hInstApp = kernel32.GetModuleHandleA(nil)
    si[0].nShow = user32.SW_SHOW
    local res = shell32.ShellExecuteExA(si)
    print(string.format("Error: 0x%02x", kernel32.GetLastError()))
    print("result:", res)
    -- Short delay to make sure the process has at least begun.
    ffi.C.Sleep(200)
    return si[0].hProcess, si[0].hwnd, si
end  

------------------------------------------------------------------------------------------------------------
local reg = nil 

function CreateWindow(name, px, py, wide, high, createtype, wndproc, exstyle, parent, menu)

    local hparent = parent or 0 
    local hmenu = menu or 0

	local hInstance = kernel32.GetModuleHandleA(nil)
	local CLASS_NAME = 'TestWindowClass'
	
	if (reg == nil ) then
	
		local classstruct = {}
		classstruct.cbSize 		= ffi.sizeof( "WNDCLASSEXA" )
		classstruct.style 		= bit.bor(user32.CS_HREDRAW, user32.CS_VREDRAW)
	
		classstruct.lpfnWndProc = wndproc

		
		classstruct.cbClsExtra 		= 0
		classstruct.cbWndExtra 		= 0
		classstruct.hInstance 		= hInstance	
		classstruct.hIcon 			= user32.LoadIconA(0, idi.APPLICATION)
		classstruct.hCursor 		= user32.LoadCursorA(0, idc.ARROW)
		classstruct.hbrBackground 	= nil
		classstruct.lpszMenuName 	= nil
		classstruct.lpszClassName 	= CLASS_NAME
		classstruct.hIconSm = 0
		
		local wndclass = ffi.new( "WNDCLASSEXA", classstruct )	
		if(reg == nil) then 
            reg = user32.RegisterClassExA( wndclass )
		
		    if (reg == 0) then
			    error('error #' .. kernel32.GetLastError())
		    end
        end
	end 

    exstyle = exstyle or 0

	local hwnd = user32.CreateWindowExA( exstyle, CLASS_NAME, name, createtype, px, py, wide, high, hparent, hmenu, hInstance, nil)	
	if (hwnd == 0) then
		error 'unable to create window'
	end
	
	user32.ShowWindow(hwnd, user32.SW_SHOW)	
    print("[HWND] ", hwnd)
	return hwnd
end

local function SetTransparentBlend( hwnd, colorref, byte, style )
    user32.SetLayeredWindowAttributes(hwnd, colorref, byte, style )
end

------------------------------------------------------------------------------------------------------------
-- To be added to UTILS
local function RemoveWindowBorders(hwnd)

    local style = user32.GetWindowLongA(hwnd, user32.GWL_STYLE)

    local minimalStyle = bit.bnot(user32.WS_NOBORDERS)
    style = bit.band( style, minimalStyle )
    -- lExStyle = bit.bor(lExStyle, minimalStyle)
    user32.SetWindowLongA(hwnd, user32.GWL_STYLE, style)    
end 

------------------------------------------------------------------------------------------------------------
-- To be added to UTILS
local pid = ffi.new("DWORD[1]")
local buffer = ffi.new("char[256]")

local function EnumWindowsProc( hwnd, lparam )

    local processData = ffi.cast("enumData *", lparam)

    -- local style = user32.GetWindowLongA(hwnd, user32.GWL_STYLE)

    if(processData[0].title == nil) then 
        pid[0] = user32.GetWindowThreadProcessId(hwnd, nil)
        if(pid[0] == processData[0].lparam[0]) then        

            ffi.fill(buffer, 0, 256)
            user32.GetWindowTextA(hwnd, buffer, 255)
            local titlestr = ffi.string(buffer)
            print(string.format("PROC MATCH: %s %d", titlestr, pid[0]))
            -- Window title?
            processData[0].lparam[0] = ffi.cast("uint64_t", hwnd)
            return false
        end
    else
        ffi.fill(buffer, 0, 256)
        user32.GetWindowTextA(hwnd, buffer, 255)
        local titlestr = ffi.string(buffer)
        if(string.len(titlestr) > 5) then
            local wintitlestr = ffi.string(processData[0].title)
            titlestr = string.gsub(titlestr, "%-", "_")
            wintitlestr = string.gsub(wintitlestr, "%-", "_")
            print(string.format("HWND MATCH: %s   %s", titlestr, wintitlestr))
            if(string.match(titlestr, wintitlestr)) then 
                processData[0].lparam[0] = ffi.cast("uint64_t", hwnd)
                return false
            end
        end
    end

	return true
end 

------------------------------------------------------------------------------------------------------------

local function GetHwndFromProcess( pid, title )
    local processdata = ffi.new("enumData[1]")
    processdata[0].lparam[0] = pid
    if(title) then 
        processdata[0].title = ffi.cast("const char *", ffi.string(title)) 
    else 
        processdata[0].title = nil
    end
    user32.EnumWindows( EnumWindowsProc, ffi.cast("LPARAM", processdata))
    return ffi.cast("HWND", processdata[0].lparam[0])
end

------------------------------------------------------------------------------------------------------------

local function GetFile( filepath )
    local data = nil
    local dfile = io.open(filepath, "r")
    if(dfile) then 
        data = dfile:read("*a")
        dfile:close()
    end
    return data
end

------------------------------------------------------------------------------------------------------------

function WindowsFileSelect()

end

------------------------------------------------------------------------------------------------------------

function WindowsFolderSelect()
    local ofn = ffi.new("OPENFILENAME")
    ofn.lStructSize = ffi.sizeof(ofn)
    ofn.lpstrFile = ffi.new( "char[512]" )
    ofn.lpstrFilter = ffi.new( "char[19]", "All\0*.*\0Text\0*.TXT\0")
    ofn.lpstrInitialDir = ffi.new( "char[3]", "C:/")
    comdlg32.GetOpenFileNameA( ofn )

    print("Selected folder: ", ffi.string(ofn.lpstrFile ), ffi.string(ofn.lpstrFilter ))
end

------------------------------------------------------------------------------------------------------------
-- Windows Hook tests - not really working well Only seems to work on Mainhwnd, I think
--   this is because the main windows belongs to this process.
local msgtbl = {}

local function MsgFilter(code, wParam, lParam)
    print(code,wParam,lParam)
    if(code < 0) then 
        return 0
    end 

    local msg = ffi.cast("MSG *", lParam)
    --print( unityhwnd, msg[0].message, msg[0].wparam, msg[0].lparam)

    if(msg[0].message == user32.WM_MOUSEMOVE or msg[0].message == user32.WM_NCMOUSEMOVE) then
        user32.PostMessageA( unityhwnd, msg[0].message, msg[0].wparam, msg[0].lparam)
    end
    table.insert( msgtbl, {hwnd=unityhwnd, msg=code, wparam=wParam, lparam=lParam})
end 

local pid = user32.GetWindowThreadProcessId( wvhwnd or 0, nil )
local hhookSysMsg = user32.SetWindowsHookExA( user32.WH_CALLWNDPROC, MsgFilter, 0, pid)

------------------------------------------------------------------------------------------------------------

return {
    CreateWindow = CreateWindow,
    SetTransparentBlend = SetTransparentBlend,
    GetHwndFromProcess = GetHwndFromProcess,
    RemoveWindowBorders = RemoveWindowBorders,
    WindowsFileSelect = WindowsFileSelect,
    WindowsFolderSelect = WindowsFolderSelect,
    execute     = execute,
    shellexecute = shellexecute,
    Sleep       = ffi.C.Sleep,
    GetFile     = GetFile,
    getDefaultBrowser   = getDefaultBrowser,

    kernel  = kernel32,
    user    = user32,
    gdi     = gdi32,
    comdlg  = comdlg32,
}