-- TIME_STAMP   2013-10-23 17:59:38   v 0.1

MySciTETools = {}

----------------------------------------------------------------------------------------------------
EvtUserlistTip = EventClass:new(Common)

-- Event Select Userlist (Search Variable by Tip)
function EvtUserlistTip:OnUserListSelection(_id, _str)
	if _id ~= 99 then return false end
	local iFirst = _str:find('[\.\.\.]') +3
	local iLast = _str:find('[\.\.\.]', iFirst +1) -1
	local var = _str:sub(iFirst,iLast)
	editor:ReplaceSel(var)
	return false
end
----------------------------------------------------------------------------------------------------

do
	local LuaCheck = function()
		local time = function() local tD = os.date("*t", os.time()) return string.format('%02d:%02d:%02d',tD.hour,tD.min,tD.sec) end
		print("+ ["..time().."] Syntax-Check <"..props['FilePath']..">") io.flush()
		local status, err = pcall(function() assert(loadfile(props['FilePath'])) end)
		if status then
			print("> ["..time().."] ------> OK") io.flush()
		else
			local filename = (props['FileNameExt']):gsub('([%(%)%.%%%+%-%*%?%[%^%$])', '%%%1') -- escape magic characters
			local line, errout = err:match(filename..":(%d+):%s+(.+)")
			editor:GotoLine(line -1) editor:LineEndExtend()  -- go to error line, make it visible, select line
			print("! ["..time().."] Line: "..line..", ERROR: "..errout) io.flush()
		end
	end --> LuaCheck
	----------------------------------------------------------------------------------------------------

	----------------------------------------------------------------------------------------------------
	-- Zwei verschiedene Funktionen:
	--
	-- VarGetTip
	--      Für Variablen werden in einer separaten Region des Skripts Tipps hinterlegt:
	--          #region - Variablen-Tipp
	--          ; $Variable1 Das ist der Tipp für $Variable1
	--          ; $Variable2 Das ist der Tipp für $Variable2
	--          ; $Variable3 Das ist der Tipp für $Variable3
	--          #endregion
	--      oder sie werden in einer Kommentarzeile mit Tag irgendwo im Skript erstellt:
	--          ;@ $Variable1 Das ist der Tipp für diese Variable
	--      oder DIREKT nach Erstellung einer Variablen kann in der Folgezeile mit '-1' auf diese verwiesen werden:
	--          $GuiMain = GuiCreate(...)
	--          ;-1 Das ist der Kommentar zur Variable in der Zeile zuvor
	--
	--      oder Tipp für Funktionen:
	--          In der Folgezeile nach Funktionskopzeile den Tipp hinterlegen mit
	--             Func _IrgendeineFunc()
	--                 ;-f Tipp zu dieser Funktion\nZweite Zeile zu diesem Tipp
	--          Ausgabe:
	--             Funktionsname( )
	--             Tipp zu dieser Funktion
	--             Zweite Zeile zu diesem Tipp
	--
	--      Es können auch Variablen gleichen Namens mehrfach verwendet werden (in unterschiedlichen Funktionen).
	--      Die Erstellung der Tipps muß dann innerhalb desselben Gültigkeitsbereiches (also in derselben Funktion oder Global) erfolgen.
	--      Das Skript sucht nach Tipps für die aktuelle Variable im aktuellen Gültigkeitsbereich. Zuerst innerhalb der Funktion,
	--      falls gerade der Cursor auf einer Variablen in einer Funktion steht, da bei gleichnamigen existierenden lokalen und globalen Variablen,
	--      die lokale Variable eine höhere Priorität hat.
	--      Es wird immer der Variablenname und der Gültigkeitsbereich ausgegeben
	--          $VarBefore  [ Global ]
	--          Das ist ein Kommentar zu $VarBefore
	--      oder
	--          $iSelf  [ Local: Func _TimeOver() ]
	--          Das ist ein Kommentar zu $iSelf
	--
	--      Mehrzeilige Tipps lassen sich erstellen mit "\n" als Zeilenumbruchzeichen im Text
	--      z.B.
	--          ;@ $VariableXY comment1\ncomment2\ncomment3
	--      Ausgabe:
	--          $VariableXY
	--          comment1
	--          comment2
	--          comment3
	--
	--      Datei speichern als "..\SciTE\LUA\VarGetTipp.lua"
	--      oder besser in eigenem Skripte-Ordner "..\VarGetTipp.lua
	--
	--      In der "SciTEUser.properties" wird ein Hotkey zum Aufruf der Funktion hinterlegt
	--         # 49 Variablen Tipp by BugFix
	--         command.name.49.*.au3=Variablen Tipp
	--         command.49.*.au3=dostring MySciTETools:VarGetTip()
	--         command.mode.49.*.au3=subsystem:lua,savebefore:no
	--         command.shortcut.49.*.au3=Ctrl+Alt+V
	--
	--  !!! Zusätzlich folgende properties-Werte erstellen !!!
	--         #~ Variablentipp / Suche Variable mit Tipp
	--         #~ Tipp als CallTip (1) oder Konsolenausgabe (0)
	--         Variable.Tipp.CallTip.*.au3=1
	--         #~ Speicherort der Lua-Skriptdateien  !! Ohne abschließenden Backslash !!
	--         Lua.User.Scripts.Path=C:\Code_AutoIt\LUA
	--         #~ Ergebnis Variablensuche per Tipp in Userliste (1) oder Konsole (0)
	--         Userlist.VarGetTip=1
	--
	--      - Cursor auf die Variable setzen
	--      - Hotkey aufrufen
	--      - Ist ein Tipp hinterlegt, wird dieser als CallTipp angezeigt oder in die Konsole ausgegeben
	----------------------------------------------------------------------------------------------------
	-- VarByTip     ( command.48.*.au3=dostring MySciTETools:VarByTip() )
	--      Sucht anhand des Begriffs in der aktuellen Editorzeile innerhalb der vorhandenen Variablentipps nach
	--      Übereinstimmungen. Treffer werden in einer Userlist zur Auswahl angezeigt.
	--      Die Variable des ausgewählten Treffers wird an der Position des Suchbegriffs im Editor eingefügt.
	--      Alternativ können alle Variablen, in deren Beschreibung das Suchwort enthalten ist, in die Konsole ausgegeben werden.
	----------------------------------------------------------------------------------------------------

	local VariableGetTip = function(_param)
		if props['FileExt']:upper() ~= 'AU3' then return end
		local GetVarFromCursor = function()
			local caret = editor.CurrentPos
			if editor.StyleAt[caret] ~= 9 then if editor.StyleAt[caret-1] ~= 9 then return caret, '' end end
			local iStart, iEnd, iPos, iEnd2, fBrace, sDebugAU3 = caret, caret, nil, nil, false, ''
			local iLine = editor:LineFromPosition(iStart)
			local iZero = editor:PositionFromLine(iLine)
			local iCol = iStart - iZero
			local sLine = editor:GetLine(iLine)
			local sRight = Trim(sLine, iCol)
			iStart = iCol
			while sLine:sub(iStart,iStart):find('[$a-zA-Z0-9_]') do iStart = iStart -1 end
			iEnd = iStart +1
			iStart = iStart +1
			while sLine:sub(iEnd,iEnd):find('[$a-zA-Z0-9_]') do sDebugAU3 = sDebugAU3..sLine:sub(iEnd,iEnd) iEnd = iEnd +1 end
			return sDebugAU3, caret
		end -- GetVarFromCursor()

		local GetFuncFromCursor = function()
			local iCaret = editor.CurrentPos
			local sSel = string.char(editor.CharAt[iCaret])
			if (string.byte(sSel) == 13) or (sSel == ' ') or (sSel == ',') then return nil end
			local sLine = editor:GetLine(editor:LineFromPosition(iCaret))
			if string.char(editor.CharAt[iCaret -1]) ~= ' ' then editor:WordLeft() end
			editor:WordRightExtend()
			local sCurrWord = editor:GetSelText()
			editor:SetSel(iCaret, iCaret)
			if sCurrWord:find('[^%w_)]') then return '' end
			if sLine:find(sCurrWord..'%s*%b()') then
				return sCurrWord, iCaret
			else
				return ''
			end
		end -- GetFuncFromCursor()

		local Output = function(sText1, sText2)
			if tonumber(props['Variable.Tipp.CallTip.*.au3']) == 1 then
				if sText2 ~= nil then sText1 = sText1..'\n'..sText2:gsub('\\n', '\n') end
				editor:CallTipShow(editor.CurrentPos, sText1)
			else
				print('!> '..sText1)
				if sText2 ~= nil then print('>> '..sText2:gsub('\\n', '\n>> ')) end
			end
		end -- Output

		local GetAllTip = function()
			local tTip = {}
			local s = editor:GetText()
			-- in vorhergehender Zeile deklariert
			for p, tip in s:gmatch('()\n[\t%s]*;%-1%s*([^\r\n]+)') do
				local _, _, var = editor:GetLine(editor:LineFromPosition(p) -1):find('(%$[%w_]+)')
				table.insert(tTip, {var,tip,p,'global'})
			end
			-- mit "at"-Tag gesetzter Tipp
			for p, var, tip in s:gmatch('()\n[\t%s]*;@%s-(%$[%w_]+)%s([^\r\n]+)') do
				table.insert(tTip, {var,tip,p,'global'})
			end
			-- in Region gesetzte Tipps
			local tReg = {}
			for p in s:gmatch("()#[Rr]egion %- [Vv]ariablen%-[Tt]ipp") do
				table.insert(tReg, p)
			end
			if table.getn(tReg) > 0 then
				for i=1,table.getn(tReg) do
					local line = editor:LineFromPosition(tReg[i])
					while not editor:GetLine(line):find('#[Ee]nd[Rr]egion') do
						line = line +1
						local _, _, var, tip = editor:GetLine(line):find(';%s-(%$[%w_]+)%s([^\r\n]+)')
						if var then table.insert(tTip, {var,tip,tReg[i],'global'}) end
					end
				end
			end
			if sParam == 'v' then
				-- Funktionstipps, nur bei Aufruf mit Parameter 'v'
				for p, tip in s:gmatch('()\n[\t%s]*;%-f%s*([^\r\n]+)') do
					local _, _, sFunc = editor:GetLine(editor:LineFromPosition(p) -1):find('([%w_]+)%s*%b()')
					table.insert(tTip, {sFunc,tip,p,sFunc})  -- Alles OK !
				end
			end
			return tTip
		end -- GetAllTip

		local GetFuncs = function()  -- Ermitteln aller Funktionsdeklarationen (Startpos, Endpos, Funktionsname)
			local tFuncs = {}
			for iStart, sFunc in editor:GetText():gmatch('()[Ff][Uu][Nn][Cc]%s+([%w_]+)%s*%b()') do
				local iLine, iEnd = editor:LineFromPosition(iStart)
				while true do
					iLine = iLine +1
					iEnd = editor:GetLine(iLine):find('[Ee][Nn][Dd][Ff][Uu][Nn][Cc]')
					if iEnd ~= nil then break end
				end
				table.insert(tFuncs, {iStart, editor:PositionFromLine(iLine), sFunc})
			end
			return tFuncs
		end -- GetFuncs

		if _param == 'v' then
			local varCurr, varPos = GetVarFromCursor()
			local fIsFuncTip = false
			if varCurr == '' then
				varCurr, varPos = GetFuncFromCursor()
				if varCurr ~= '' then fIsFuncTip = true end
			end
			local tFuncs, tTip, fMatch, fMatchFunc, fInFunc, sFunc, sTip = GetFuncs(), GetAllTip(), false, false, false

			local GetTip = function(_var, _scope)
				_scope = _scope or 'global'
				for i=1,table.getn(tTip) do
					if tTip[i][1]:lower() == _var:lower() and tTip[i][4] == _scope then return tTip[i][2] end
				end
				return nil
			end -- GetTip

			if table.getn(tTip) > 0 and table.getn(tFuncs) > 0 then  -- den Tipps die Funktionen zuordnen
				for i=1,table.getn(tTip) do
					for j=1,table.getn(tFuncs) do
						if tTip[i][3] >= tFuncs[j][1] and tTip[i][3] <= tFuncs[j][2] then
							tTip[i][4] = tFuncs[j][3]
							break
						end
					end
				end
			end

			local IsVarInFunc = function(_var, _pos)
				if table.getn(tFuncs) == 0 then return false, nil end
				for i=1,table.getn(tFuncs) do
					if _pos >= tFuncs[i][1] and _pos <= tFuncs[i][2] then
						return true, tFuncs[i][3]  -- Rückgabe: True/False, Funktionsname
					end
				end
				return false, nil
			end -- IsVarInFunc

			if table.getn(tTip) == 0 then
				Output('Keine Tipps hinterlegt!')
			else
				if varCurr == '' then
					Output('Cursor steht nicht auf einer Variablen\noder einem Funktionsaufruf!')
				else
					if not fIsFuncTip then fInFunc, sFunc = IsVarInFunc(varCurr, varPos) end
					if fInFunc then
						sTip =  GetTip(varCurr, sFunc)
						if sTip ~= nil then
							Output(varCurr..'  [ Local: Func '..sFunc..'() ]', sTip)
						else
							sTip =  GetTip(varCurr, 'global')
							if sTip ~= nil then
								Output(varCurr..'  [ Global ]', sTip)
							else
								Output(varCurr, 'Es ist kein Tipp hinterlegt!')
							end
						end
					elseif fIsFuncTip then
						sTip =  GetTip(varCurr, varCurr)
						if sTip ~= nil then
							Output(varCurr..'( )', sTip)
						else
							Output(varCurr..'( )', 'Es ist kein Tipp hinterlegt!')
						end
					else
						sTip =  GetTip(varCurr, 'global')
						if sTip ~= nil then
							Output(varCurr..'  [ Global ]', sTip)
						else
							Output(varCurr, 'Es ist kein Tipp hinterlegt!')
						end
					end
				end
			end

		elseif _param == 's' then
			local tTip = GetAllTip()  -- {variable, tipp, position, scope}
			local sWord = AutoItTools:GetWord()
			local tFound, sFound, sep, iS, sTipN = {}, '', ';'
			if sWord:gsub('[%s]+', '') == '' or table.getn(tTip) == 0 then return end
			local fList = false
			if props['Userlist.VarGetTip'] == '1' then fList = true end
			local OutputGrouped = function(_fList)
				local iMax, sOut, sLineEnd = 0, '', '\n'
				if _fList then sLineEnd = sep end
				for i=1,table.getn(tFound) do if tFound[i][2]:len() > iMax then iMax = tFound[i][2]:len() end end
				for i=1,table.getn(tFound) do sOut = sOut..tFound[i][1]..'...'..tFound[i][2]..('.'):rep(iMax-tFound[i][2]:len()+3)..tFound[i][3]..sLineEnd end
				return sOut
			end
			local SelectCurrentWord = function()
				local iCaret = editor.CurrentPos
				local iLine = editor:LineFromPosition(iCaret)
				local iLine1stPos = editor:PositionFromLine(iLine)
				local iDiffPos = iCaret - iLine1stPos +1
				local sLine = editor:GetLine(iLine)
				local sWord = props["CurrentWord"]
				if sWord == '' then return end
				local s, p = sLine:find(sWord)
				local tMatch = {}
				while s ~= nil do
					table.insert(tMatch, {s,p})
					s, p = sLine:find(sWord, s+1)
				end
				if table.getn(tMatch) > 0 then
					for i=1, table.getn(tMatch) do
						if iDiffPos >= tMatch[i][1] and iDiffPos <= tMatch[i][2] then
							editor:SetSel(iLine1stPos-1 +tMatch[i][1], iLine1stPos +tMatch[i][2])
							break
						end
					end
				end
			end
			for i=1,table.getn(tTip) do
				iS = tTip[i][2]:lower():find(sWord:lower())
				if iS then
					sTipN = tTip[i][2]:gsub('\\n', ' \[LINEBREAK\] ')
					table.insert(tFound, {tTip[i][4], tTip[i][1], sTipN})
				end
			end
			if table.getn(tFound) > 0 then sFound = OutputGrouped(fList) else sFound = 'Kein Tipp mit diesem Begriff vorhanden.' end
			SelectCurrentWord()
			if not fList then print(sFound) editor:ReplaceSel('') return end
			editor.AutoCSeparator = string.byte(sep)
			editor:UserListShow(99, sFound)
			editor.AutoCSeparator = string.byte(' ')
		end
	end --> VariableGetTip

	local VarGetTip = function() VariableGetTip('v') end
	local VarByTip  = function() VariableGetTip('s') end
	----------------------------------------------------------------------------------------------------

	----------------------------------------------------------------------------------------------------
	--          Time
	-- _part    - nil (default) returns "hh:mm:ss"
	--          - h / m / s     returns "hh" / "mm" / "ss"
	-- _number  - returns h / m / s as number
	----------------------------------------------------------------------------------------------------
	local Time = function(_part, _number)
		local datetable, retValue = os.date("*t", os.time())
		if _part == nil then
			return string.format('%02d:%02d:%02d', datetable.hour, datetable.min, datetable.sec)
		else
			if _part:lower() == 'h' then retValue = string.format('%02d', datetable.hour)
			elseif _part:lower() == 'm' then retValue = string.format('%02d', datetable.min)
			elseif _part:lower() == 's' then retValue = string.format('%02d', datetable.sec) end
			if _number ~= nil then retValue = tonumber(retValue) end
			return retValue
		end
	end --> Time
	----------------------------------------------------------------------------------------------------

	----------------------------------------------------------------------------------------------------
	-- Detects if char at given editor position has comment style
	----------------------------------------------------------------------------------------------------
	local IsComment = function(_pos)
		local tComment = {1,2} if tComment[editor.StyleAt[_pos]] == nil then return false else return true end
	end
	----------------------------------------------------------------------------------------------------

	----------------------------------------------------------------------------------------------------
	-- Detects if char at given editor position has white space style
	----------------------------------------------------------------------------------------------------
	local IsWhiteSpace = function(pos)
		if editor.StyleAt[pos] == 0 then return true else return false end
	end
	----------------------------------------------------------------------------------------------------

	----------------------------------------------------------------------------------------------------
	-- Creates an insensitive string (combination of upper and lower chars: "[aA][bB]")
	----------------------------------------------------------------------------------------------------
	local InSensitive = function(_s)
		_s = _s:gsub("%a", function(_c) return string.format("[%s%s]", _c:upper(),_c:lower()) end) return _s
	end
	----------------------------------------------------------------------------------------------------

	----------------------------------------------------------------------------------------------------
	-- Trim _i chars from string _s
	-- Positive integer trim from left, negative from right side
	-- _s       String to trim
	-- _i       Number of chars to trim
	-- _fspace  "true" deletes after trimming existing space chars on trimming side
	----------------------------------------------------------------------------------------------------
	local Trim = function(_s, _i, _fspace)
		local iLen, tPatt, iPatt = _s:len(), {'^%s*', '%s*$'}, 1   -- {leftPatt,rightPatt}
		local sTrim
		if _i == nil then _i = 1 end
		if _i >= iLen then return '' end
		if _i == 0 then return _s
		elseif _i < 0 then sTrim = _s:sub(1, iLen + _i) iPatt = 2  -- trim from right
		else sTrim = _s:sub(_i + 1, -1) end                        -- trim from left
		if _fspace then sTrim = sTrim:gsub(tPatt[iPatt], '') end
		return sTrim
	end  --> Trim
	----------------------------------------------------------------------------------------------------

	----------------------------------------------------------------------------------------------------
	--[[ Splits up a string into substrings depending on the given delimiters.
	_delimiter:  nil= each character will be returned as an element
	...........  If no delimiters were found, the full string is returned
	_flag.....:  nil= each character in the delimiter string will mark where to split the string
	...........  1= entire delimiter string is needed to mark the split
	...........  2= empty substrings are ignored (can be combined with other values)
	Note......:  Use only raw strings for delimiters, no pattern! To split on tab, newline, or carriage return, use
	..........:  their wildcards: \t = tab; \r = carriage return; \n = line feed; \r\n = carriage return/line feed
	Return....:  table with splitted string, count of elements (= 1 if no delimiter was found)
	--]]
	----------------------------------------------------------------------------------------------------
	local StringSplit = function(_string, _delimiter, _flag)
		local EscapeMagic = function(_delim)
			if _delim:find('[%(%)%.%%%+%-%*%?%[%^%$]') then
				return _delim:gsub('([%(%)%.%%%+%-%*%?%[%^%$])', '%%%1')
			else return _delim end
		end
		local tString, tSplit, n = {}, {}, 1
		_string = tostring(_string)
		if _delimiter ~= nil then
			_delimiter = tostring(_delimiter)
		else
			for k in _string:gmatch('(.)') do table.insert(tString, k) end
			return tString, #tString
		end

		_delimiter = EscapeMagic(_delimiter)
		local tPos, patt = {}, '()([' .. _delimiter .. '])'
		if (_flag == 1 or _flag == 3) then
			patt = '()(' .. _delimiter .. ')'
			n = _delimiter:len()
		end
		local pLast, sLen, sub = 1, _string:len(), ''
		local insEmpty = not((_flag == 2 or _flag == 3) or false)
		for p in _string:gmatch(patt) do
			if p > (pLast -n) then sub = _string:sub(pLast, p-1) pLast = p +n end
			if (sub == '' and insEmpty) or sub ~= '' then table.insert(tSplit, sub) end
			sub = ''
		end
		if (sLen-n) >= pLast then sub = _string:sub(pLast, sLen) end
		if (sub == '' and insEmpty) or sub ~= '' then table.insert(tSplit, sub) end
		return tSplit, #tSplit
	end
	----------------------------------------------------------------------------------------------------

											   -- SYNTAX
	MySciTETools.LuaCheck     = LuaCheck       -- Modul.LuaCheck()  or Modul:LuaCheck()
	MySciTETools.VarGetTip    = VarGetTip      -- Modul.VarGetTip() or Modul:VarGetTip()
	MySciTETools.VarByTip     = VarByTip       -- Modul.VarByTip()  or Modul:VarByTip()
	MySciTETools.Time         = Time           -- Modul.Time(parameter)
	MySciTETools.IsComment    = IsComment      -- Modul.IsComment(pos)
	MySciTETools.IsWhiteSpace = IsWhiteSpace   -- Modul.IsWhiteSpace(pos)
	MySciTETools.InSensitive  = InSensitive    -- Modul.InSensitive(string)
	MySciTETools.Trim         = Trim           -- Modul.Trim(parameters)
	MySciTETools.Split        = StringSplit    -- Modul.Split(parameters)
end