script_name('pro100_binder')
script_author("RuBin")
vkeys = require "lib.vkeys"
sampev = require 'lib.samp.events'

DIALOG_ID = 7761
DIALOG_ID_NO_LIST = 7762

local ffi = require "ffi"
ffi.cdef[[
     void keybd_event(int keycode, int scancode, int flags, int extra);
]]

function EmulateKey(key, isDown)
    if not isDown then
        ffi.C.keybd_event(key, 0, 2, 0)
    else
        ffi.C.keybd_event(key, 0, 0, 0)
    end
end

function sampev.onServerMessage(color, message)
    if message:find('SMS: .+Отправитель: .+%[(%d+)%]') then
        replace["{id_in_sms}"] = message:match('SMS: .+Отправитель: .+%[(%d+)%]')
    end
    if message:find('SMS: .+Получатель: .+%[(%d+)%]') then
        replace["{id_out_sms}"] = message:match('SMS: .+Получатель: .+%[(%d+)%]')
    end
end

function main()
	if not isSampLoaded() or not isSampfuncsLoaded() then return end
	while not isSampAvailable() do wait(100) end
	
	settings_load()
	command_show_dialog()

	replace = {
		["{target_id}"] = "0",
		["{target_rp_name}"] = " ",
		["{target_name}"] = " ",
		["{id_in_sms}"] = "0",
		["{id_out_sms}"] = "0"
	}

	key_press = {}
	key_press_counter = 0
	is_key_press = false
	is_block_gamekeys = false

	while true do
		wait(0)
		while_target_check()
		while_key_check()
		while_show_dialog()
	end
end

function settings_load()
	AdressConfig = string.format("%s\\moonloader\\config ", getGameDirectory())
	AdressJson = string.format("%s\\moonloader\\config\\pro100_binder.json", getGameDirectory())

    local table_std = {
		{ cmd = {'/mm'}, enter = true, key = {'VK_M'} }
	}
    if not doesFileExist(AdressJson) then
        local file, error = io.open(AdressJson, "w")
        if file ~= nil then
            file:write(encodeJson(table_std))
            file:flush()
            io.close(file)
        else
            sampAddChatMessage(error, -1)
        end
    end

    try(
        function()
            local file, error = io.open(AdressJson, "r")
            if file then
                settings = decodeJson(file:read("*a"))
                io.close(file)
            end
        end, 
        function(e)
            sampAddChatMessage("[pro100_binder] Ошибка чтения конфига! Сбрасываю конфиг!", 0xff0000)
            local file, error = io.open(AdressJson, "w")
            if file ~= nil then
                file:write(encodeJson(table_std))
                file:flush()
                io.close(file)
            end
        end
    )


    if settings ~= nil then
        if error_ini ~= nil then
            sampAddChatMessage("[pro100_binder] Конфиг был успешно загружен!", 0xff0000)
            error_ini = nil
        end
        for k,v in pairs(table_std) do
            if settings[k] == nil then
                settings[k] = v
            end
            for i, s in pairs(v) do
                if settings[k][i] == nil then
                    settings[k][i] = s
                end
            end
        end
        settings_save()
    else
        error_ini = true
        sampAddChatMessage("[pro100_binder] Ошибка чтения конфига! Пробую ещё раз прочесть", 0xff0000)
        settings_load()
    end
end

function settings_save()
    local file, error = io.open(AdressJson, "w")
    if file ~= nil then
        file:write(encodeJson(settings))
        file:flush()
        io.close(file)
    else
        sampAddChatMessage(error, -1)
    end
end

function try(f, catch_f)
	local status, exception = pcall(f)
	if not status then
	  catch_f(exception)
	end
end

function while_target_check()
	local result, handle = getCharPlayerIsTargeting(PLAYER_HANDLE)
	if result then
		local result2, id = sampGetPlayerIdByCharHandle(handle)
		if result2 then
			local name = sampGetPlayerNickname(id)
			replace["{target_id}"] = id
			replace["{target_rp_name}"] = name:gsub("_", " ")
			replace["{target_name}"] = name
		end
	end
end

function while_key_check()
	local editDialogActive = false
	if sampIsDialogActive() and sampGetCurrentDialogType() == 1 then
		editDialogActive = true 
	end

	if not sampIsScoreboardOpen() and not sampIsChatInputActive() and not isSampfuncsConsoleActive() and not editDialogActive then
		if isKeyDown(vkeys["VK_MENU"]) then
			if not is_block_gamekeys then
				is_block_gamekeys = true
				setPlayerDuckButton(PLAYER_HANDLE, false)
				setPlayerEnterCarButton(PLAYER_HANDLE, false)
			end
		else
			if is_block_gamekeys then
				is_block_gamekeys = false
				setPlayerDuckButton(PLAYER_HANDLE, true)
				setPlayerEnterCarButton(PLAYER_HANDLE, true)
			end
		end


		local isPress = false
		local isMenu = false
		for k, v in pairs(vkeys) do
			if isKeyDown(v) and k ~= "VK_ESCAPE" and k ~= "VK_RETURN" and k ~= "VK_LBUTTON" and k ~= "VK_W" and k ~= "VK_S" and k ~= "VK_A" and k ~= "VK_D" then
				isPress = true
				if key_press[k] == nil then
					if k:find("MENU") then
						local enter_press = false
						for k,v in pairs(key_press) do
							if k:find("MENU") then
								enter_press = true
							end
						end
						if not enter_press then
							key_press["VK_MENU"] = "VK_MENU"
							key_press_counter = key_press_counter + 1
						end
					else
						key_press[k] = v
						key_press_counter = key_press_counter + 1
					end
				end
			end
		end
		
		if isPress ~= is_key_press and not isPress then
			for i = 1, #settings do 
				local down_counter = 0
				local alt = false
				for k = 1, #settings[i].key do
					if settings[i].key[k]:find("MENU") then
						alt = true
					end
					if key_press[settings[i].key[k]] ~= nil then
						down_counter = down_counter + 1
					end
				end
				if down_counter == #settings[i].key and key_press_counter == #settings[i].key then
					while_send_array(alt, settings[i].enter, settings[i].cmd)
					repeat wait(0) until not isKeyDown(vkeys[settings[i].key[#settings[i].key]])
				end
			end
			key_press = {}
			key_press_counter = 0
		end
		is_key_press = isPress
	end
end

function check_stop_bind()
	if isKeyDown(vkeys["VK_N"]) then
		return true
	end
end

function while_send_array(alt, enter, copy)

	local array = {}
	local command = 0
	for i = 1, #copy do
		array[i] = copy[i]
		if array[i]:find("wait (%d+)") then
			local ms = array[i]:match("wait (%d+)")
			if #("wait "..ms) ~= #array[i] then
				command = command + 1
			end
		else
			command = command + 1
		end
	end

	local command_count = 0
	repeat
		wait(0)
		local text = array[1]
		local wait_time = -1

		if text:find("wait (%d+)") then
			local ms = text:match("wait (%d+)")
			if #("wait "..ms) == #text then
				wait_time = tonumber(ms)
			end
		end

		if wait_time ~= -1 then
			local sleep = os.clock() * 1000
			repeat 
				wait(0)
			until os.clock() * 1000 - sleep > wait_time or check_stop_bind() or not enter
			if check_stop_bind() then
				break
			end
			table.remove(array, 1)
		else

			for k,v in pairs(replace) do 
				if text:find(k) then
					text = text:gsub(k, v)
				end
			end

			if alt and enter then
				sampSendChat(text)
				command_count = command_count + 1
			else
				local chattext = sampGetChatInputText()
				sampSetChatInputText(text)
				sampSetChatInputEnabled(true)
				if enter then
					repeat
						wait(0)
					until sampIsChatInputActive()
					EmulateKey(13, true)
					EmulateKey(13, false)
				end
				command_count = command_count + 1
			end
			repeat
				wait(0)
			until not sampIsChatInputActive()
			if command > 1 then
				printStringNow(string.format("BIND COUNT: %s/%s~n~Press N to stop", command_count, command), 1000)
				if check_stop_bind() then
					break
				end
			end
			table.remove(array, 1)
		end
	until #array <= 0
end

function command_show_dialog()
	sampRegisterChatCommand('binder',function()
		show_dialog("main")
	end)
end

function show_dialog(param)
	if param == "main" then
		show_main_dialog()
	end
end

function get_text_keys_array(i)
	local key_name = ""
	for s = 1, #settings[i].key do
		if (settings[i].key[s]:find("MENU") and not key_name:find("ALT")) or not settings[i].key[s]:find("MENU") then
			key_name = key_name..settings[i].key[s]:gsub("VK_",""):gsub("MENU", "ALT")..(settings[i].key[s + 1] ~= nil and " + " or "")
		end
	end
	return key_name
end

function show_main_dialog()
	dialogArray = {}

	dialogArray[#dialogArray + 1] = {
		text = "Создать бинд\t \t ",
		func = function(button, list, input)
			if button == 1 then
				show_create_dialog()
			end
		end
	}

	for i = 1, #settings do 
		local key_name = get_text_keys_array(i)

		dialogArray[#dialogArray + 1] = {
			text = " ["..i.."] "..key_name.."\t"..(settings[i].cmd[1] ~= nil and settings[i].cmd[1] or "nil").." ["..#settings[i].cmd.."]\t"..(settings[i].enter and "ENTER" or " "),
			func = function(button, list, input)
				if button == 1 then
					show_bind_dialog(i, key_name)
					return
				end
			end
		}
	end

	local text = ""
	for k,v in pairs(dialogArray) do
		text = text..v.text.."\n"
	end

	sampShowDialog(DIALOG_ID,"PRO100_BINDER",text,"Выбрать","Закрыть",4)
end

function show_bind_dialog(bind_id, key_name)
	if settings[bind_id] == nil then return end

	local reset = function(button, list, input)
		if button == 1 then
			show_bind_dialog(bind_id, key_name)
		else
			show_main_dialog()
		end
	end

	dialogArray = {}

	dialogArray[#dialogArray + 1] = {
		text = "Активация: "..key_name,
		func = reset
	}

	dialogArray[#dialogArray + 1] = {
		text = "Авто-отправка: "..(settings[bind_id].enter and "ON" or "OFF"),
		func = reset
	}

	dialogArray[#dialogArray + 1] = {
		text = " ",
		func = reset
	}

	dialogArray[#dialogArray + 1] = {
		text = "[1] Удалить",
		func = function(button, list, input)
			if button == 1 then
				table.remove(settings, bind_id)
				settings_save()
			end
			show_main_dialog()
		end
	}

	dialogArray[#dialogArray + 1] = {
		text = "[2] Редактировать",
		func = function(button, list, input)
			if button == 1 then
				show_create_dialog(bind_id)
			end
		end
	}

	dialogArray[#dialogArray + 1] = {
		text = "\nВвод: \n"..get_text_array(settings[bind_id].cmd),
		func = reset
	}

	local text = ""
	for k,v in pairs(dialogArray) do
		text = text..v.text.."\n"
	end

	sampShowDialog(DIALOG_ID,"PRO100_BINDER",text,"Выбрать","Закрыть",4)
end

function get_text_keys(keys)
	local text_keys = ""
	for i = 1, #keys do
		if (keys[i]:find("MENU") and not text_keys:find("ALT")) or not keys[i]:find("MENU") then
			text_keys = text_keys..keys[i]:gsub("VK_", ""):gsub("MENU", "ALT")..(keys[i+1] ~= nil and " + " or "")
		end
	end
	return text_keys
end

function get_text_array(array)
	local text= ""
	for i = 1, #array do
		text = text..array[i].."\n"
	end
	return text
end

function show_create_dialog(param)
	edit_key = param
	dialogArray = {}
	local keys = {}

	if edit_key ~= nil and settings[edit_key] ~= nil then
		keys = settings[edit_key].key
	end

	local timer_show_dialog = os.time()
	local result, button = false, 0
	local refresh = true
	local global_isPress = false
	repeat
		wait(0)

		local isPress = false
		for k, v in pairs(vkeys) do
			if isKeyDown(v) and k ~= "VK_ESCAPE" and k ~= "VK_RETURN" and k ~= "VK_LBUTTON" then
				isPress = true

				if k:find("MENU") then
					local result_search = true
					for i = 1, #keys do
						if keys[i]:find("MENU") then
							result_search = false
						end
					end

					if result_search then
						keys[#keys + 1] = "VK_MENU"
						refresh = true
					end
				else
					local result_search = false
					for i = 1, #keys do
						if keys[i] == k then
							result_search = true
						end
					end

					if not result_search then
						keys[#keys + 1] = k
						refresh = true
					end
				end
			end
		end
		
		if isPress ~= global_isPress and isPress then
			keys = {}
		end
		global_isPress = isPress

		if os.time() - timer_show_dialog > 0 or refresh then
			refresh = false
			timer_show_dialog = os.time()

			local text_keys = get_text_keys(keys)

			local text = string.format("{FFFFFF}Нажмите клавишу или сочитание клавиш\nИспользуется: %s", text_keys)
			sampShowDialog(DIALOG_ID,"PRO100_BINDER",text,"Далее","Назад",0)
		end
		
		local res, button, list, input = sampHasDialogRespond(DIALOG_ID)
		if res then
			if button == 1 then
				if #keys > 0 then
					local array = {}
					if edit_key ~= nil and settings[edit_key] ~= nil then
						array = settings[edit_key].cmd
					end
					show_create2_dialog(keys, array)
					break
				else
					show_create_dialog(param)
					break
				end
			else
				show_main_dialog()
				break
			end
		end
	until result
end

function show_create2_dialog(keys, array)
	dialogArray = {}
	dialogArray[#dialogArray + 1] = {
		text = "Активация: "..get_text_keys(keys),
		func = function(button, list, input)
			show_create_dialog(edit_key)
		end
	}

	dialogArray[#dialogArray + 1] = {
		text = " ",
		func = function(button, list, input)
			show_create_dialog(edit_key)
		end
	}

	dialogArray[#dialogArray + 1] = {
		text = " [1] Добавить строку",
		func = function(button, list, input)
			if button == 1 then
				show_edit("Добавить строку", keys, array)
			else
				show_create_dialog(edit_key)
			end
		end
	}

	dialogArray[#dialogArray + 1] = {
		text = " [2] Добавить задержку (ms)",
		func = function(button, list, input)
			if button == 1 then
				show_edit("Добавить задержку (ms)", keys, array)
			else
				show_create_dialog(edit_key)
			end
		end
	}

	dialogArray[#dialogArray + 1] = {
		text = " [3] Далее",
		func = function(button, list, input)
			if button == 1 then
				show_create3_dialog(keys, array)
			else
				show_create_dialog(edit_key)
			end
		end
	}

	dialogArray[#dialogArray + 1] = {
		text = " [4] Отмена",
		func = function(button, list, input)
			show_create_dialog(edit_key)
		end
	}

	dialogArray[#dialogArray + 1] = {
		text = " ",
		func = function(button, list, input)
			show_create_dialog(edit_key)
		end
	}

	if #array > 0 then
		dialogArray[#dialogArray + 1] = {
			text = "Список текста	(click to del)",
			func = function(button, list, input)
				show_create_dialog(edit_key)
			end
		}
	end

	for i = 1, #array do
		dialogArray[#dialogArray + 1] = {
			text = "   "..array[i],
			func = function(button, list, input)
				if button == 1 then
					table.remove(array, i)
				end
				show_create2_dialog(keys, array)
			end
		}
	end

	dialogArray[#dialogArray + 1] = {
		text = " ",
		func = function(button, list, input)
			show_create_dialog(edit_key)
		end
	}

	local text = ""
	for i = 1, #dialogArray do
		text = text..dialogArray[i].text.."\n"
	end

	sampShowDialog(DIALOG_ID,"PRO100_BINDER",text,"Далее","Отмена",2)
end

function show_edit(text, keys, array)
	dialogArray = {}

	if text == "Добавить строку" then

		text = string.format("%s\nПоддерживаются встроенные переменные!\n\n Таргет - это когда вы прицелились в человека (Зеленый треугольник над головой)\n Тогда скрипт запоминает данные этого игрока и вы можете их использовать!\n\t{target_id} - ID игрока\n\t{target_rp_name} - RP ник игрока\n\t{target_name} - Ник игрока", text)

		local server_name = sampGetCurrentServerName()
		if server_name:find("SRP") or server_name:find("Samp%-Rp") then
			text = string.format("%s\n\nПоддерживаются переменные для Samp-Rp!\n\t{id_in_sms} - ID игрока с последнего входящего СМС\n\t{id_out_sms} - ID игрока с последнего отправленного Вами СМС", text)
		end

		dialogArray[1] = {
			text = " [1] Добавить строку",
			func = function(button, list, input)
				if button == 1 then
					if #input > 0 then
						array[#array + 1] = input
					end
					show_create2_dialog(keys, array)
				end
				show_create2_dialog(keys, array)
			end
		}
	end

	if text == "Добавить задержку (ms)" then
		dialogArray[1] = {
			text = " [2] Добавить задержку (ms)",
			func = function(button, list, input)
				if button == 1 then
					if input:find("%d+") then
						local ms = input:match("(%d+)")
						if #ms == #input then
							array[#array + 1] = "wait "..input:match("(%d+)")
						end
					end
				end
				show_create2_dialog(keys, array)
			end
		}
	end

	sampShowDialog(DIALOG_ID_NO_LIST,"PRO100_BINDER",text,"Отправить","Отмена",1)
end

function show_create3_dialog(keys, array)
	local text_keys = get_text_keys(keys)
	local text_command = get_text_array(array)
	local text = string.format("{FFFFFF}Активация: %s\nВвод: %s\n\n  Отправлять автоматически?\n\n      (Иначе вставка в чат)", text_keys, text_command)

	dialogArray = {}
	dialogArray[1] = {
		text = "",
		func = function(button, list, input)
			if button == 1 then
				show_create4_dialog(keys, array, true)
			else
				show_create4_dialog(keys, array, false)
			end
		end
	}

	sampShowDialog(DIALOG_ID_NO_LIST,"PRO100_BINDER",text,"Да","Нет",0)
end

function show_create4_dialog(keys, array, enter)
	local text_keys = get_text_keys(keys)
	local text_command = get_text_array(array)

	local text = string.format("{FFFFFF}Активация: %s\nВвод: %s\nАвто-отправка: %s\n\n  Сохранить?", text_keys, text_command, (enter and "ON" or "OFF"))

	dialogArray = {}
	dialogArray[1] = {
		text = "",
		func = function(button, list, input)
			if button == 1 then
				if edit_key ~= nil and settings[edit_key] ~= nil then
					settings[edit_key] = { cmd = array, enter = enter, key = keys }
				else
					settings[#settings + 1] = { cmd = array, enter = enter, key = keys }
				end
				settings_save()
			end
			show_main_dialog()
		end
	}

	sampShowDialog(DIALOG_ID_NO_LIST,"PRO100_BINDER",text,"YES","NO",0)
end


function while_show_dialog()
	local result, button, list, input = sampHasDialogRespond(DIALOG_ID)
	if result and dialogArray ~= nil and dialogArray[list + 1] then
		dialogArray[list + 1].func(button, list, input)
	end
	local result, button, list, input = sampHasDialogRespond(DIALOG_ID_NO_LIST)
	if result and dialogArray ~= nil and dialogArray[1] then
		dialogArray[1].func(button, list, input)
	end
end