local compile_state='ready'
local annot_flag=false
local compile_menu
local compile_ext
local compile_obj
local compile_res
local prj2api_state='ready'
local prj2api_obj

local AUTOC_STYLES = {
    [3] = {5,11,16,32}
}
local PREPROCESSOR_STYLE=9
local IGNORE_CASE = false
local MIN_PREFIX_LEN = 2
local MIN_IDENTIFIER_LEN = 3

local IDENTIFIER_PATTERN = "[a-z_][a-z_0-9]+"
local INCLUDE_PATTERN = "[\"<][a-z_0-9/.]+[\">]"

local shouldAutoComplete=function(pos) return false end

function run_docompile(ext, hext, opt)
  compile_ext=ext
  spawner.verbose(false)
  compile_obj = spawner.new(Utf8ToAnsi('"'..props['SciteDefaultHome']..'\\compile.cmd" "'..props['FileDir']..'" '..ext..' temp\\temp "'..props['compile.flags']..'" "-include temp\\tempinc.'..hext..'" '..opt))
  compile_obj:set_output('CompileProcessChunk')
  compile_obj:set_result('CompileProcessStop')
  compile_state='running'
  compile_res=''
  return compile_obj:run()
end

function run_prj2api()
  if prj2api_state=='ready' then
    spawner.verbose(false)
    prj2api_obj = spawner.new(Utf8ToAnsi('"'..props['SciteDefaultHome']..'\\prj2api.cmd" "'..props['FileDir']..'" "'..(props["APIPath"] or "")..'"'))
    prj2api_obj:set_output('Prj2apiProcessChunk')
    prj2api_obj:set_result('Prj2apiProcessStop')
    prj2api_state='running'
    return prj2api_obj:run()
  else
    return false
  end
end


function CompileProcessChunk(s)
   compile_res=compile_res..s
end

function Prj2apiProcessChunk(s)
   compile_res=compile_res..s
end

function buildPrefix(p, sfx1, sfx2)
  if p then
    local h2=buffer.apiBase[p]
    local rez="\t"..p..sfx1
    if sfx2 ~= "" then
      rez=rez.."\n\t"..p..sfx2
    end
    if h2 then
      while h2 ~= "" do
        local pos=h2:find(" ")
        local h3=""
        if pos then
          h3=h2:sub(1,pos-1)
          h2=h2:sub(pos+1)
        else
          h3=h2
          h2=""
        end  		
        rez=rez.."\n\t"..h3..sfx1
        if sfx2 ~= "" then
          rez=rez.."\n\t"..h3..sfx2
        end
      end
    end
--    trace(p.."\n"..rez.."\n")
    return rez
  else
    return ""
  end
end

function CompileProcessStop(res)
  if compile_state ~= 'ready' then
    compile_state='ready'
    if annot_flag then
      editor:AnnotationClearAll()
      editor.AnnotationVisible=0
      annot_flag=false
    end
    local err_flg=true
    local i,j
    if compile_ext=='cpp' or compile_ext=='hpp' then
      i,j=compile_res:find("error: could not convert [^\n]+ to '_abracadabra_type'")
      if i then
        local prefix=compile_res:sub(i,j)
        local prefix2=""
        i,j=prefix:find("{aka .+}' to")
        if i then
          prefix2=prefix:sub(i+5,j-5)
	end
        i,j=prefix:find(" from '.+' to")
        if i then
          prefix=prefix:sub(i+7,j-4)
          prefix=prefix:gsub(" {aka .+}","")
        else
          prefix=""
        end
        local const_flag=false;
        if prefix:find("^const ") then
          prefix=prefix:gsub("^const ","")
          const_flag=true
        end
        prefix=prefix:gsub("^std::","")
        prefix=prefix:gsub("^__cxx11::","")
        prefix=prefix:gsub("::",":")
        prefix=prefix:gsub("%b<>","")
        local sfx2=""
        if not const_flag then 
	  sfx2="::" 
 	end
--        trace(prefix.."\n"..prefix2.."\n")
	if prefix2~="" then
          prefix2=prefix2:gsub("^std::","")
          prefix2=prefix2:gsub("^__cxx11::","")
          prefix2=prefix2:gsub("::",":")
          prefix2=prefix2:gsub("%b<>","")
	  prefix2=buildPrefix(prefix2,"=:",sfx2)
	end
        if prefix~="" then
          if compile_menu==233 then
            if editor:CallTipActive() then
              editor:CallTipCancel()
            end    
          end
          prefix=buildPrefix(prefix,"=:",sfx2)
	  if prefix2~="" then
	    prefix=prefix.."\n"..prefix2
	  end
          buffer.APIprefix=prefix
          scite.MenuCommand(compile_menu)
        end
        err_flg=false
      end
    elseif compile_ext=='c' or compile_ext=='h' then
      i,j=compile_res:find("note: expected '_abracadabra_type [^\n]+")
      if i then
        local prefix=compile_res:sub(i,j)
        i,j=prefix:find("is of type '.+'")
        if i then
           prefix=prefix:sub(i+12,j-1)
        else
           prefix=""
        end
        prefix=prefix:gsub("^const ","")
        prefix=prefix:gsub("^struct ","")
        prefix=prefix:gsub(" {aka .+","")
--        trace(prefix.."\n")
        if prefix~="" then
          if compile_menu==233 then
            if editor:CallTipActive() then
              editor:CallTipCancel()
            end    
          end
          buffer.APIprefix="\t"..prefix.."::"
          scite.MenuCommand(compile_menu)
        end
        err_flg=false
      end
    end
    if err_flg then
      i,j=compile_res:find("temp\\temp%.%a+:%d+:%d+: error: [^\n]+")
      if i then
        local txt=compile_res:sub(i,j)
        txt=txt:gsub("[^:]+:","",1)
        i,j=txt:find("^%d+")
        local errline=tonumber(txt:sub(i,j))-1
        txt=txt:gsub("%d+:%d+: error: ","")
        editor.AnnotationText[errline]=txt
        editor.AnnotationVisible=2
        annot_flag=true
      end
--      trace("Found errors in the code above. See temp/temp.log\n")
--      local out=io.open(props['FileDir'].."/temp/temp.log","w")
--      out:write(compile_res)
--      out:close()
    end
  end
end

local function is_style(s,v)
  return s==v or s==v+64
end
                                                                                                   
local apiCacheExt = {}

local function getApiIncludes()
    local ext = props["FileExt"]
    if apiCacheExt[ext..'Includes'] then
      return apiCacheExt[ext..'Includes'],apiCacheExt[ext..'Names'],apiCacheExt[ext..'IncList'],apiCacheExt[ext..'Base']
    end
    local apiIncludes = {}
    local apiNames = {}
    local apiIncList = ""
    local apiFiles = props["APIPath"] or ""
    local apiBase = {}

    apiFiles:gsub("[^;]+", function(apiFile)
        local file=io.open(apiFile,"r")
        local str
        local pos
	local idx=0
        local notname="!# "
        while 1 do
          str=file:read("*l")
          if idx==0 and str and str:sub(1,2) == "# " then
            apiIncList=apiIncList.."\t#include "..(str:sub(3,-1):gsub(" ","\n\t#include ")).."\n"
          elseif str and str:sub(1,1) == "!" then
	    pos=str:find(" ")
            if pos then
              local h1=str:sub(2,pos-1)
	      local h2=str:sub(pos+1)
              h2=h2:gsub(" #<#>","")
	      apiIncludes[h1]=h2
            end
          elseif str and str:sub(1,1) == ">" then
	    pos=str:find(" ")
            if pos then
              local h1=str:sub(2,pos-1)
	      local h2=str:sub(pos+1)
              h2=h2:gsub(" #<#>","")
	      apiBase[h1]=h2
            end
	  elseif str then
            local first=str:sub(1,1)
            if not notname:find(first,1,true) then
  	      pos=str:find("#<")
              local incl=""
              if pos then
                incl=str:sub(pos+2)
                pos=incl:find(">")
  	      if pos then
  	        incl=incl:sub(1,pos-1)
                else
                  incl=""
                end
              end
              if str:sub(1,1)=="\t" then
                str=str:sub(2):gsub("[=:$][:%%]","::")
              end
              str=str:gsub("[( \t].*","")
              if str~="" then
                apiNames[str]=incl
              end
            end
	  else
	    break
          end
          idx=idx+1
        end
        file:close()
        return ""
    end)
    apiCacheExt[ext..'Includes'] = apiIncludes
    apiCacheExt[ext..'Names']=apiNames
    apiCacheExt[ext..'IncList']=apiIncList
    apiCacheExt[ext..'Base']=apiBase
    return apiIncludes,apiNames,apiIncList,apiBase
end

local function setLexerSpecificStuff()
    if AUTOC_STYLES[editor.Lexer] then
        shouldAutoComplete = function(pos)
            local t=editor.StyleAt[pos]
            if AUTOC_STYLES[editor.Lexer] then
              for k,v in pairs(AUTOC_STYLES[editor.Lexer]) do 
                 if is_style(t,v) then 
                   return true
                 end
              end
            end
            return false
        end
    else
        shouldAutoComplete = function(pos) return true end
    end
end

local function loadProjectApi(apiIncludes,apiNames)
  local file=io.open((props["FileDir"] or "").."\\project.api","r")
  local res=""
  if not file then
    return res
  end
  local str
  local pos
  local idx=0
  while 1 do
    str=file:read("*l")
    if idx==0 and str and str:sub(1,2) == "# " then
      res=res.."\t#include "..(str:sub(3,-1):gsub(" ","\n\t#include ")).."\n"
    elseif str and str:sub(1,1) == "!" then
      pos=str:find(" ")
      if pos then
        local h1=str:sub(2,pos-1)
        local h2=str:sub(pos+1)
        h2=h2:gsub(" #<#>","")
        apiIncludes[h1]=h2
      end
    elseif str then
      res=res..str.."\n"
      pos=str:find("#<")
      local incl=""
      if pos then
        incl=str:sub(pos+2)
        pos=incl:find(">")
        if pos then
          incl=incl:sub(1,pos-1)
        else
          incl=""
        end
      end
      str=str:gsub("[( \t].*","")
      if str~="" then
        apiNames[str]=incl
      end
    else
      break
    end
    idx=idx+1
  end
  file:close()  
  return res
end
local function lineStart(line)
  if line==0 then
    return 0
  else
    local pos=editor.LineEndPosition[line-1]
    if editor.CharAt[pos]==13 then
      pos=pos+2
    else
      pos=pos+1
    end
    return pos
  end
end
local function buildLocal(start_pos)
  local start_line=editor:LineFromPosition(start_pos)
  if buffer.contextLine and buffer.contextLine==start_line then
    return
  end
  buffer.context=""
  local names={}
  local unique = {}
  local line=start_line
  local startPos, endPos
  endPos=start_pos
  local str
  while line>0 do
    endPos=lineStart(line)
    if editor.CharAt[endPos]<=32 or editor.CharAt[endPos]==123 then
      line=line-1
    else
      str=editor:GetLine(line)
      if str:find("^public:") or str:find("^protected:") or str:find("^private:") then
        line=line-1
      else
        break
      end
    end
  end
  str=editor:GetLine(line)
  str=str:gsub("%b<>","")
  local classname=""
  if str:find("%sclass%s") or str:find("^class%s") then
     _,_,classname=str:find("class%s+([%w_]+)")
  elseif str:find("%sstruct%s") or str:find("^struct%s") then
     _,_,classname=str:find("struct%s+([%w_]+)")
  elseif str:find("::[%w_]+[(]") then
     str=str:gsub("::[%w_]+[(].*","")
     str=str:gsub(".*%s","")
     classname=str:gsub("::",":")
  end
  if classname and classname~="" then
--    trace(classname.."::\n")
    buffer.context="\t"..classname.."=:\n\t"..classname.."=%\n\t"..classname..":%\n\t"..classname.."::\n\t"..classname.."$%\n\t"..classname.."$:\n\n"
  end
  buffer.contextLine=start_line
--  line=start_line+1
--  while line<editor.LineCount do
--    startPos=lineStart(line)
--    if editor.CharAt[startPos]<=32 then
--      line=line+1
--    else
--      break
--    end
--  end
--  buffer.contextEnd=startPos
  endPos = endPos-1
  while true do
    startPos, endPos = editor:findtext(IDENTIFIER_PATTERN, SCFIND_REGEXP, endPos + 1)
    if not startPos or endPos>=start_pos then
      break
    end
    if shouldAutoComplete(startPos) then
       if endPos-startPos+1 >= MIN_IDENTIFIER_LEN then
         local name = editor:textrange(startPos, endPos)
         if classname and classname~="" and buffer.apiNames[classname.."::"..name] then
         elseif buffer.apiNames then
           local incl=buffer.apiNames[name] or buffer.apiNames[name.."<>"] or buffer.apiNames[name.."<,>"]
           if incl then
             if incl~="" and not buffer.includes[incl] then
                unique[name] = name
             end
           else
             unique[name] = name
           end
         end
       end
    end
  end
  for _,name in pairs(unique) do
    table.insert(names, name)
  end
  buffer.APIlocal=table.concat(names,"\n").."\n"
end

local function buildNames()
    setLexerSpecificStuff()
    local apiIncList,apiIncludes0,apiNames0,apiBase
    apiIncludes0,apiNames0,apiIncList,apiBase=getApiIncludes()
    local apiIncludes={}
    local apiNames={}
    for key,val in pairs(apiIncludes0) do 
       apiIncludes[key] = val
    end
    for key,val in pairs(apiNames0) do 
       apiNames[key] = val
    end
    local prjapi=loadProjectApi(apiIncludes,apiNames)
    local includes={}
    if apiIncludes[props["FileNameExt"]] then
               apiIncludes[props["FileNameExt"]]:gsub("[^ ]+", function(addinc)
                    includes[addinc]=true
                  end)
    end
    buffer.APIchanged='yes'

    buffer.APIproject=apiIncList..prjapi

    buffer.APIinclude=props["FileNameExt"].."\n"
    for key,val in pairs(includes) do
        buffer.APIinclude=buffer.APIinclude..key.."\n"
    end
    local ext=props["FileExt"]
    if ext=='h' or ext=='c' then
      buffer.APIinclude=buffer.APIinclude.."c\n"
    end
    if ext=='cpp' or ext=='hpp' then
      buffer.APIinclude=buffer.APIinclude.."c++\n"
    end
    buffer.apiNames=apiNames
    buffer.apiBase=apiBase
    buffer.contextLine=nil
    buffer.context=nil
    buffer.includes=includes
end

function Prj2apiProcessStop(res)
  if prj2api_state ~= 'ready' then
    prj2api_state='ready'
    buildNames()
  end
end

local function buildProjectApi()
  if props['FileExt']~="" then
    run_prj2api()
  end
end
local function is_context_style(style)
  return is_style(style,10) or is_style(style,11)
end
local function is_contstring(style)
  return is_style(style,2) or is_style(style,12)
end

local function find_par(str,i,sp,p1,p2)
  local k=0
  local j=i
  while j>0 do
    if is_style(editor.StyleAt[sp+j-1],10) or is_style(editor.StyleAt[sp+j-1],0) then
      if str:sub(j,j)==p2 then
         k=k+1
      elseif str:sub(j,j)==p1 then
         k=k-1
         if k== -1 then
           return j
         end
      end
    end
    j=j-1
  end
  return i+1
end
local function find_name(str,i,sp)
  local j=i
  while j>0 do
    if j>=2 and str:sub(j-1,j)=='<>' then
      j=j-2
    elseif j>=3 and str:sub(j-2,j)=='<,>' then
      j=j-3
    end
    while j>0 and (is_style(editor.StyleAt[sp+j-1],11) or is_style(editor.StyleAt[sp+j-1],0) and str:sub(j,j):match('[%w_]')) do
      j=j-1
    end
    if j>=2 and str:sub(j-1,j)=='::' then
      j=j-2
    else
      return j+1
    end
  end
  return j+1
end
local function find_start(str,i,sp)
  local j
  while i>0 do
    if str:sub(i,i)==')' then
       j=find_par(str,i-1,sp,'(',')')
       if j<i then
         i=j-1
       else
         return i+1
       end
    elseif str:sub(i,i)==']' then
       j=find_par(str,i-1,sp,'[',']')
       if j<i then
         i=j-1
       else
         return i+1
       end
    elseif is_style(editor.StyleAt[sp+i-1],5) and i>=4 and str:sub(i-3,i)=='this' then
       return i-3
    elseif is_style(editor.StyleAt[sp+i-1],11) then
       j=find_name(str,i,sp)
       if j>1 and str:sub(j-1,j-1)=='.' then
         i=j-2
       elseif j>2 and str:sub(j-2,j-1)=='->' then
         i=j-3
       else
         return j
       end
    else
      return i+1
    end
  end
  return i+1
end
local function set_prefix(str,i,start_pos,char,menu)
 if compile_state=='ready' and props["FileExt"]:find("^[hc]p*$") then
  if char=='>' then
    i=i-2
  else
    i=i-1
  end
  j=find_start(str,i,start_pos)
  if i<j then
    return true
  end
  local filename=props['FileDir']..'/temp/temp.'..props['FileExt']
  if not scite_FileExists(filename) then
    local r = spawner.popen(Utf8ToAnsi('mkdir "'..props['FileDir']..'\\temp"'))
    r:read('*a')
    r:close()
  end
  local text=editor:GetText()
  local expr=str:sub(j,i)
  if char=='>' then
    expr='*('..expr..')'
  end
  text=text:sub(1,start_pos)
  local inclist="struct _abracadabra_type {};\n"
  local hext="hpp"
  if props['FileExt']=="c" or props['FileExt']=="h" then
    hext="h"
    inclist=inclist.."typedef struct _abracadabra_type _abracadabra_type;\n"
  end
  inclist=inclist.."int _abracadabra_cast(_abracadabra_type);\n"

  text=text:gsub("[^\n]+",function(inc)
       if inc:sub(1,8)=="#include" then
         inclist=inclist..inc.."\n"
         return ""
       else
         return inc
       end
     end)
  local hname=props['FileDir']..'/temp/tempinc.'..hext
  local inf=io.open(hname,'rb')
  local hstr=''
  local rewr_flg=true
  if inf then
    hstr=inf:read('*all')
    inf:close()
    if hstr==inclist then
      rewr_flg=false
    end
  end
  local out
  local opt=""
  if rewr_flg then
    out=io.open(hname,'wb')
    out:write(inclist)
    out:close()
    opt="temp\\tempinc."..hext
  end
  out=io.open(filename,"wb")
  out:write(text)
  out:write(str:sub(1,j-1)..'_abracadabra_cast('..expr..');')
  out:close()
  compile_menu=menu
  run_docompile(props['FileExt'],hext,opt)
 end
end

local function set_prefix_openpar(str,i,start_pos)
  i=i-1
  local j=find_name(str,i,start_pos)
  local char
  if j<=i then
    if j>2 and str:sub(j-1,j-1)=='.' then
      char='.'
    elseif j>3 and str:sub(j-2,j-1)=='->' then
      char='>'
    else
      local prefix=str:sub(j,i)
      prefix=prefix:gsub("^std::","")
      prefix=prefix:gsub("%b<>","")
      prefix=prefix:gsub("[%w_]+$","")
      if prefix~="" then
        prefix=prefix:gsub("::",":")
        prefix=prefix:gsub(":$","")
        buffer.APIprefix=buildPrefix(prefix,"$:","")
      else
        buffer.APIprefix=""
        return
      end
      scite.MenuCommand(232)
      return true
    end
    set_prefix(str,j-1,start_pos,char,232)
    return
  end
end
local function set_prefix_name(str,i,start_pos)
  local j=find_name(str,i,start_pos)
  local char
  if j<=i then
    if j>2 and str:sub(j-1,j-1)=='.' then
      char='.'
    elseif j>3 and str:sub(j-2,j-1)=='->' then
      char='>'
    else
      local prefix=str:sub(j,i)
      prefix=prefix:gsub("[%w_]+$","")
      prefix=prefix:gsub("^std::","")
      if prefix~="" then
        buffer.APIprefix="\t"..prefix
      else
        buildLocal(start_pos+i-1)
        buffer.APIprefix=buffer.context or ""
      end
      scite.MenuCommand(233)
      return true
    end
    set_prefix(str,j-1,start_pos,char,233)
    return
  end
end

local function handleChar(char, calledByHotkey)
    if annot_flag then
      editor:AnnotationClearAll()
      editor.AnnotationVisible=0
      annot_flag=false
    end
    local pos = editor.CurrentPos
    local spec_char='"<>.( #:)'
    if compile_state=='running' then
      return
    end
    local line=editor:LineFromPosition(pos)
    local str=editor:GetLine(line)
    if str==nil then
      return
    end
    str=str:gsub("\r?\n$","")
    local i=str:len()+pos-editor.LineEndPosition[line]
    local start_pos=editor.LineEndPosition[line]-str:len()
    local j
    if spec_char:find(char,1,true) then
       if i==1 and char=='#' then
         buffer.APIprefix='#'
         scite.MenuCommand(233)
         return true
       elseif (char=='"' and not str:sub(1,i-1):find('"') or char=='<') and str:find("^#include") then
         buffer.APIprefix="\t#include "
         scite.MenuCommand(233)
         return true
       elseif i>1 and (char=='.' or char=='>' and str:sub(i-1,i-1)=='-') and is_context_style(editor.StyleAt[pos-2]) then
         if str:sub(1,1)=='#' then
           return
         end
         set_prefix(str,i,start_pos,char,233)
         return
       elseif i>1 and char==':' and str:sub(i-1,i-1)==':' then
         j=find_name(str,i-2,start_pos)
         if i-2<j then
           return
         end
         local prefix=str:sub(j,i):gsub("^std::","")
         if prefix~="" then
           prefix=prefix:gsub("::",":")
           prefix=prefix:gsub(":$","")
           if is_style(editor.StyleAt[start_pos],11) or is_style(editor.StyleAt[start_pos],5) or is_style(editor.StyleAt[start_pos],0) and str:sub(1,1):match('[%w_]') then
             buffer.APIprefix="\t"..prefix.."$:\n\t"..prefix.."$%\n\t"..prefix.."=:\n\t"..prefix.."=%\n\t"..prefix.."::\n\t"..prefix..":%"
           else
             buffer.APIprefix=buildPrefix(prefix,"$:","")
           end
         else
           buffer.APIprefix=""
           return
         end
         scite.MenuCommand(233)
         return true
       elseif i>=7 and char==' ' and str:sub(i-6,i-1)=='struct' and is_style(editor.StyleAt[pos-7],5) and (props['FileExt']=='c' or props['FileExt']=='h') then
         buffer.APIprefix=" struct@"
         scite.MenuCommand(233)
         return true
       elseif i>=10 and char==' ' and str:sub(i-9,i-1)=='namespace' and is_style(editor.StyleAt[pos-10],5) and (props['FileExt']=='cpp' or props['FileExt']=='hpp') then
         buffer.APIprefix=" namespace@"
         scite.MenuCommand(233)
         return true
       elseif i>1 and char==')' and not is_contstring(editor.StyleAt[pos-2]) then
         j=find_par(str,i,start_pos,'(',')')
         if j<i then
           return set_prefix_openpar(str,j,start_pos)
         end
       elseif i>1 and char=='(' and not is_contstring(editor.StyleAt[pos-2]) then
         return set_prefix_openpar(str,i,start_pos)
       end
       return
    end
    if editor:AutoCActive() then
      return 
    end
    if char:find("[%w_]") then
      local p1,p2
      p1,p2=str:sub(1,i):find("[%w_]+$")
      if p1 and p2-p1+1>=MIN_PREFIX_LEN and shouldAutoComplete(start_pos+p1-1) then
        return set_prefix_name(str,i,start_pos)
      end
    end
end

local function handleKey(key,shift,ctrl,alt)
  if key==8 and annot_flag then
    editor:AnnotationClearAll()
    editor.AnnotationVisible=0
    annot_flag=false
  end
end

scite_OnChar(handleChar)
scite_OnKey(handleKey)
scite_OnSave(buildProjectApi)
scite_OnSwitchFile(function()
        if not buffer.APIinclude then
            buildNames()
        else
            setLexerSpecificStuff()
        end
    end)
scite_OnOpen(function()
        editor:Colourise(0, editor.Length)
        buildProjectApi()
    end)
