#!/usr/bin/env lua
--[[ This script generate pdf documentation from LuaCV sources and OpenCV tex documentations, so you need OpenCV sources to generate it. 
  License:
    Documentation and this script are under LuaCV license.
  Generated content:
    - License pages
    - LuaCV function references
    - LuaCV object references
    - OpenCV constants

  Output:
    - PDF luacv_doc.pdf

             ======================================
             !!!   ONLY WORKING WITH LUA 5.1    !!!
             ======================================
]]
lfs=require "lfs"

LuaObjects={}
LuaObjectsDesc={}
LuaObjectsAttr={}
LuaFunctions={}
LuaFunctionsDesc={}
LuaConstants={}
--LuaModules={'imgproc'}
LuaModules={'core','calib3d','highgui','imgproc','features2d','video','legacy','objdetect'}
OUT_NAME='luacv_doc.tex'
SRC_PATH='../src/'
OPENCV_DIR='/home/jura/OpenCV-2.2.0/'
OPENCV_DOC=OPENCV_DIR..'doc/'
OPENCV_SRC=OPENCV_DIR..'modules/'
LATEX="pdflatex -interaction=batchmode "
TEMPLATES='templates/'

function getLatexChars(str)
  return string.gsub(str,'\_','\\\_')
end

function CheckParenthesis(str)
  count=0
  for i in string.gmatch(str,'(.)') do
    if string.find(i,'[{}]')~=nil then
      count=count+1
    end
  end
  if count%2==1 then 
    str=string.sub(str,1,-2)
  end
  return str
end

function GetFunctionsDescription()
  for files in lfs.dir(OPENCV_DOC) do
    if string.match(files,".+%.[Tt][Ee][Xx]$")~=nil then
      local func={}
      local offset=0
      local buffer=GetFileContents(OPENCV_DOC..files)
      repeat 
        local starts,ends,name=string.find(buffer,'\\cvC[pPy]+Func{([A-Za-z0-9]+)}',offset)
        if starts==nil then break end
        table.insert(func,{name,starts,ends})
        offset=ends+1
      until false

      for i=1,#func do
        LuaFunctionsDesc[func[i][1]]={}
        local latex=string.sub(buffer,func[i][2],i==#func and -1 or func[i+1][2]-1)
        LuaFunctionsDesc[func[i][1]]['short']=string.match(latex,'^\\cvC[pPy]+Func{'..func[i][1]..'}\n([^\\]+)\n\\cvdef')
        LuaFunctionsDesc[func[i][1]]['args']={}
        for n,d in string.gmatch(latex,'\\cvarg{([^}]+)}{([^\n]+)}\n') do
          n=string.gsub(n,'\\_','_')
          LuaFunctionsDesc[func[i][1]]['args'][n]=CheckParenthesis(string.gsub(string.gsub(d,'\\cv[a-zA-Z0-9]+{','\\textbf{'),'\\cross{','\\textbf{'))

        end
      end
    end
  end
  print('-- Getting function description')
end

function GetLuaObjects()
  path=SRC_PATH.."objects/"
  for name in lfs.dir(path) do
    if (string.find(name,"%.h")~= nil ) then
      name=string.sub(name,1,#name-2)
      mname=string.upper(name)
      LuaObjects[mname]=name
    end
  end 
  print('-- Getting LuaCV objects')
end

function GetFileContents(fname)
  file=io.open(fname,"r") or error("can't open file "..fname)
  str=file:read("*a") 
  file:close()
  print('-- Getting '..fname..' contents.')
  return str
end

function GetFunctionsHeader(filename)
  LuaFunctions={}
  local file=io.open(filename or error("nil filename"),"r")
  for line in file:lines() do
    fdesc=string.match(line,'const\ +char\ +f\_msg%[%]="([^;]+);')  
    if (fdesc~=nil) then
      if (string.find(fdesc,"#")==nil) then 
        fdesc=string.gsub(string.sub(fdesc,1,#fdesc-1),'"?([A-Z0-9]+)_NAME"?',LuaObjects)
        LuaFunctions[string.match(fdesc,' ?([A-Za-z0-9]+)%(')]=getLatexChars(fdesc)
      end
    end
  end
  file:close()
  print('-- Getting functions header.')
end

function DescriptionSyntax(name,dsc)
  local replace={}
  replace[name]='\\textbf{'..name..'}'
  for k,v in pairs(LuaObjects) do
    replace[v]='\\hyperlink{objects:'..v..'}{'..v..'}'
  end
  local list={'userdata','int','num','string'}
  for i,l in pairs(list) do
    replace[l]='\\hyperlink{objects:'..l..'}{'..l..'}'
  end
  local sub=string.gsub(dsc,'([A-Za-z0-9]+)',replace)
  print('-- Making highlighting for functions.')
  return sub
end


function FunctionArgs(name,desc)
  if (string.match(desc,name..'%(%)')) then return "{\\em none} & function doesn't have input arguments.\\\\\\hline" end
  local starts,ends=string.find(desc,name)
  local dsc=string.sub(desc,ends+2,-2)
  local list={}
  list['userdata']='An unspecified C pointer to memory'
  list['int']='An integer type number'
  list['num']='A floating point number'
  list['string']='An array of characters'
  list['table']='A lua table'
  list['func']='A lua function'
  list['float']='A floating point number which must be float'

  for k,v in pairs(LuaObjects) do
    list[v]='A wrappered OpenCV object of type '..v
  end
  local str=''
  local default=''
  for obj,n in string.gmatch(dsc,'([A-Za-z\\_0-9%[%]]+)[ ]? ([A-Za-z0-9=\\_()]+)[,]?') do
    if string.find(n,"=") then
      local it=0
      for find in string.gmatch(n,'([A-Za-z0-9\\_()+|]+)') do
        if (it==0) then 
          n=find
          it=it+1
        else
          default=find
        end
      end
      default=' with default value \\textit{'..default..'}'
    else 
      default=''
    end
    if string.find(obj,"%[%]") then
      default=' which is an array '..default
      obj=string.sub(obj,1,-3)
    end
    if (LuaFunctionsDesc[name]~=nil)and (LuaFunctionsDesc[name]['args'][string.gsub(n,'\\_','_')]~=nil) then
      str=str..'\\textbf{'..n..'} & '..LuaFunctionsDesc[name]['args'][string.gsub(n,'\\_','_')]..' \\\\\\hline\n' 
    else
      if list[obj]~=nil then
        str=str..'\\textbf{'..n..'} & '..list[obj]..default..' \\\\\\hline\n' 
      
       end
    end
  end
  print('-- Generating functions arguments.')
  return str
end

function FunctionReturns(name,desc)
  local starts,ends=string.find(desc,name)
  if (starts==1) then return "\\textbf{none} \- function doesn't return anything.\\\\" end
  local dsc=string.sub(desc,1,starts-1)
  local str=''
  local list={}
  list['userdata']='An unspecified C pointer to memory'
  list['int']='An integer type number defined by architecture'
  list['num']='A floating point number defined by lua.h header, in default it is type double'
  list['string']='An array of characters'
  list['float']='A floating point number which must be float'

  for k,v in pairs(LuaObjects) do
    list[v]='A wrappered OpenCV object of type '..v
  end

  for ret in string.gmatch(dsc,'([A-Za-z0-9]+)[ ,]?') do
    if list[ret]~= nil then
    str=str..'\\hyperlink{objects:'..ret..'}{'..ret..'} \- '..list[ret]..' \\\\' 
    end
  end
  print('-- Generating functions return values.')
  return str
end

function GenerateFunctionReference(ofile)
  GetLuaObjects()
  GetFunctionsDescription()

  local func_templ=GetFileContents(TEMPLATES..'func.tex')
  for i,mod in pairs(LuaModules) do 
    ofile:write('\\input{'..mod..'}\n')
    GetFunctionsHeader(SRC_PATH..'lua_'..mod..'.cpp')
    tmpfile=io.open(mod..'.tex',"w+")
    tmpfile:write('\\section{Module '..mod..'}\n') 
    repl={}
    local list={}
    for k,v in pairs(LuaFunctions) do
      list[#list+1]=k
    end
    table.sort(list)

    for i=1,#list do 
      local name=list[i]
      local desc=LuaFunctions[list[i]]
      repl['#FUNCNAME#']=name
      repl['#LINK#']='functions:'..mod..":"..name
      repl['#FUNCDESC#']=DescriptionSyntax(name,desc)
      repl['#FILENAME#']='lua\\\_'..mod..'.cpp'
      repl['#FUNCRETS#']=FunctionReturns(name,desc)
      repl['#FUNCARGS#']=FunctionArgs(name,desc)
      repl['#FUNCSHORTDESC#']=LuaFunctionsDesc[name]~=nil and LuaFunctionsDesc[name]['short'] or "Non details detected."

      local sub=string.gsub(func_templ,'(#[A-Z]+#)',repl)
      tmpfile:write(sub)
    end
    tmpfile:close() 
  end
  print('-- Making functions module latex files.')
end

function GetObjectDescription()
  for files in lfs.dir(OPENCV_DOC) do
    if string.match(files,".+%.[Tt][Ee][Xx]$")~=nil then
      local obj={}
      local offset=0
      local buffer=GetFileContents(OPENCV_DOC..files)
      repeat 
        local starts,ends,name=string.find(buffer,'\\cvclass{([A-Za-z0-9]+)}\\?l?a?b?e?l?{?[A-Za-z0-9]*}?',offset)
        if starts==nil then break end
        table.insert(obj,{name,starts,ends})
        offset=ends+1
      until false

      for i=1,#obj do
        starts,ends,find=string.find(buffer,'([^\n]+)',obj[i][3]+1)
        while(true) do
          if (string.match(find,'\\ifC*')~=nil) then
            starts,ends,find=string.find(buffer,'([^\n]+)',ends+1)
          else
            break
          end
        end
        if (string.match(find,'\\begin{lstlisting}')==nil) then
          LuaObjectsDesc[obj[i][1]]=string.gsub(string.gsub(find,'\\cv[a-zA-Z0-9]+{','\\textbf{'),'\\cross{','\\textbf{')
        end
      end
    end
  end
  print('-- Getting object description.')
end

function GetObjectAttributes(name)
  local tmp=GetFileContents(SRC_PATH..'objects/'..name..'.cpp')
  local str=''
  for attr in string.gmatch(tmp,'methodReg%(([a-zA-Z0-9_]+),'..name..'%)') do
    local typ=string.match(tmp,'push([^f][^%(]+)%(L ?,[^-]+%-?>?'..attr..'%);')
    if (typ~=nil) then
      str=str..'\n \\item \\hyperlink{objects:'..typ..'}{'..typ..'} ~\- ~ '..getLatexChars(attr)
    end
  end

  print('-- Getting object attributes.')

  if (str=='') then 
    return '\\item No attributes detected.'
  else
    return str
  end
end


function GenerateObjectReference(ofile)
  local obj_templ=GetFileContents(TEMPLATES..'obj.tex')
  ofile:write('\\input{objs}\n')
  tmpfile=io.open('objs.tex',"w+")
  repl={}

  GetObjectDescription()
  local list={}
  for i,j in pairs(LuaObjects) do
    list[#list+1]=i
  end

  table.sort(list)

  for i=1,#list do 
    local mname=list[i]
    local name=LuaObjects[list[i]]
    repl['#NAME#']=name
    repl['#LINK#']='objects:'..name
    repl['#SDESC#']=LuaObjectsDesc[name] or 'No description detected'
    repl['#FILE#']='objects/'..name..'.cpp'
    repl['#ATTRS#']=GetObjectAttributes(name)
    sub=string.gsub(obj_templ,'(#[A-Z]+#)',repl)
    tmpfile:write(sub)
  end
  tmpfile:close()
  print('-- Making object latex.')
end

function GenerateConstantReference(ofile)
  local templ=GetFileContents(TEMPLATES..'const.tex')
  for i,mod in pairs(LuaModules) do 
    ofile:write('\\input{const_'..mod..'}\n')
    local tmpfile=io.open('const_'..mod..'.tex','w+')
    local tmp=GetFileContents(SRC_PATH..'lua_'..mod..'.cpp')
    starts,ends=string.find(tmp,'const luacv_var +'..mod..'_var%[%]=\n{')
    local sub=string.sub(tmp,ends+1)

    for co in string.gmatch(sub,'varReg[(]([^)]+)[)],?') do
      LuaConstants[co]=''
    end
    local list={}
    for index,j in pairs(LuaConstants) do
      list[#list+1]=index
    end

    table.sort(list)

    GetConstantValues()
    local str=''

    for index=1,#list do 
      local n=list[index]
      local v=LuaConstants[list[index]]
      str=str..'\\textbf{'..getLatexChars(n)..'} &  \\lstinline[language=C]$'..v..'$ \\\\ \\hline \n'
    end
    repl={}
    repl['#NAME#']=mod
    repl['#SDESC#']='List of constants from '..mod..' module'
    repl['#FILE#']='lua\\\_'..mod..'.cpp'
    repl['#CONST#']=str
    sub=string.gsub(templ,'(#[A-Z]+#)',repl)
    tmpfile:write(sub)
    tmpfile:close()
    LuaConstants={}
  end
  print('-- Making constants latex section.')
end

function GetConstantValues()
  for i,mod in pairs(LuaModules) do
    local path=OPENCV_SRC..mod..'/include/opencv2/'..mod
    for files in lfs.dir(path) do
      local starts,ends,file=string.find(files,'(.+%.hp?p?)$')
      if file~=nil then
        local tmpfile=GetFileContents(path..'/'..file)
        for name,value in string.gmatch(tmpfile,'#define +([A-Z0-9a-z_]+) +([^\n]+)\n') 
        do
          if (LuaConstants[name]~=nil) then
            LuaConstants[name]=value
          end
        end

        for name,value in string.gmatch(tmpfile,' *([A-Z0-9a-z_]+) *= ?([^\n,]+),?') 
        do
          if (LuaConstants[name]~=nil) then
            LuaConstants[name]=value
          end
        end
     end
   end
  end
  print('-- Getting constants values.')
end


function GenerateLatex()
  local start_time=os.time()
  ofile=io.open(OUT_NAME,"w+")
  str=string.gsub(GetFileContents(TEMPLATES..'header.tex'),"#DATE#",os.date())
  ofile:write(str)

  list={'license', 'changelog', 'modules'}
  for i,name in pairs(list) do
    ofile:write('\\input{'..TEMPLATES..name..'}\n')
  end

  GenerateFunctionReference(ofile)
  ofile:write('\\input{'..TEMPLATES..'objects}\n')
  GenerateObjectReference(ofile)
  ofile:write('\\input{'..TEMPLATES..'constants}\n')
  GenerateConstantReference(ofile)

  ofile:write(GetFileContents(TEMPLATES..'footer.tex'))
  ofile:close()
  print('-- Latex files generated.')
  print('-- Documentation was generated in '..os.difftime(os.time(),start_time)..' secondes')
end

function BuildPDF()
  local start_time=os.time()
  os.execute(LATEX..OUT_NAME)
  os.execute(LATEX..OUT_NAME)
  print(' -- PDF was compiled in '..os.difftime(os.time(),start_time)..' secondes')
end

function RemoveTemp()
  ext={'.tex','.toc','.aux','.out','.log','.idx'}
  for files in lfs.dir('./') do
    for _,j in pairs(ext) do
      if string.find(files,j) ~=nil then
        print('-- Removing temp file '..files)
        os.remove(files)
        break
      end
    end
  end
end

GenerateLatex()
BuildPDF()
RemoveTemp()
