//==============================================================================
//  Common functions for clijasshelper and (grimoire)jasshelper.
//==============================================================================
unit cUnit;

//==============================================================================
//  INTERFACE
//==============================================================================
interface
uses Windows, Tlhelp32, ExternalUsage, jasshelperconfigfile, JASSerException;

//==============================================================================
//  Only this procedure is public, it calls jasshelper.
//  Use it from your main function.
//==============================================================================
procedure Main();

//==============================================================================
//  VARIABLES
//==============================================================================
type
  IDoAbout = procedure();
  //IDoRequire = procedure();
var
  DoAbout : IDoAbout;
  //DoRequire : IDoRequire; // wewarlock?

  // TODO: wrap in read-only functions
  usedExternal:boolean = false;

    mpq:THandle;
  war3mapj,f1,f2,f3,folder:string;
  map: string;

//==============================================================================
//  IMPLEMENTATION
//==============================================================================
implementation
uses WinExec,SysUtils, JHInterface, vxFileSys, jhErrors, JHParams,
     ImportPaths, storm, Preprocessor, Structs, ReturnFix, ShadowHelper,
     Inliner, TempUnit, PreprocessorImport, Constants;

const
  INPUT_J = 'logs\input_war3map.j';
  OUTPUT_J = 'logs\output_war3map.j';

var
  blizzardj,commonj: string;

  JASSHELPER_PATH:string='';


    temi:integer=0;
    stage:integer=1;

    CONFIG_PATH:string='';




//==============================================================================
//  Saves a map inside backup directory
//==============================================================================
procedure takeBackup(const m:string);
var dat:string;
    procedure save(const i:char);
    var
       s:string;
    begin
        s:='backups\'+i+'.w3x';
        if (FileExists(s)) then DeleteFile(pchar(s));
        ProStatus('thismap --> ' + s);
        windows.CopyFile(pchar(m),pchar(s),false);
    end;

begin
    dat:='';
    if (not DirectoryExists('backups')) then CreateDirectory('backups',nil);

    if(FileExists('backups\backupsdata.txt')) then
        dat := LoadFile('backups\backupsdata.txt');
    if (Length(dat)<36) then begin //something wrong in there
        save('0');
        dat:='123456789abcdefghijklmnopqrstuvwxyz0';
        SaveFile('backups\backupsdata.txt',dat);
    end else begin
        save(dat[1]);
        dat:=Copy(dat,2,35)+dat[1];
        SaveFile('backups\backupsdata.txt',dat);
    end;

end;

//==============================================================================
//  Vexorian obviously copied this from somewhere...
//==============================================================================
FUNCTION GetProcessID(ExeName:STRING):DWORD;
 VAR pe: TProcessEntry32;
     h: THandle;
     test: boolean;
 BEGIN
  GetProcessID:=0;
  ExeName:=  LowerCase(ExeName);

  h:=CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS,0);
  TRY
   pe.dwSize:=SizeOf(TProcessEntry32);
   test:=Process32First(h,pe);
   WHILE test DO
    BEGIN
//    MessageDlg(ExtractFileName(pe.szExeFile),mtWarning,[mbOK],0    );
     IF (GetCurrentProcessId<>pe.th32ProcessID) and (ExtractFileName(pe.szExeFile)=ExeName) THEN GetProcessID:=pe.th32ProcessID;
     test:=Process32Next(h,pe);
    END;
   FINALLY
    CloseHandle(h);
   END;
 END;

//==============================================================================
//  Terminates all processes with given fileName.
//  This is used to terminated old instances of jasshelper when you run it again.
//==============================================================================
function TerminateAll(const fileName:string):boolean;
var
c: THandle;
dw:DWORD;
begin
    dw:=getprocessid(fileName);
    Result:=false;

    while (dw<>0) do begin
        Result:=true;
        c := openprocess(PROCESS_ALL_ACCESS,FALSE, dw);
        TerminateProcess(C,0);
        closehandle(C);
        dw:=getprocessid(fileName);
    end;
end;

//==============================================================================
function FixScriptFileName(const f:string):string;
var
    i,L,st:integer;
begin
    L:=Length(f);
    st:=0;
    for i := L downto 1 do
        if(f[i]='/') or (f[i]='\') then begin
            st:=i;
            break;
        end;
    Result:=Copy(f, st+1, L);
    Result:=LowerCase(Result);
end;

//==============================================================================
procedure dopjasserrors(const war3mapj:string; const errors:string; const tries:integer=0);
var
   f:textfile;
   line,sect:string;
   blizz,comm,maa:boolean;
   L,i,k,n,counter:integer;

    procedure seek(var ii:integer);
    begin
        while(ii<=L) and not((line[ii]=':') and ( (ii=L) or(line[ii+1]<>'\') )) do ii:=ii+1;
    end;

begin
   ProStatus('PJASS ERROR(S) FOUND!');

   counter:=0;
   blizz:=false;
   comm:=false;
   maa:=false;

   try
       assignfile(f,errors);
       filemode:=fmOpenRead;
       Reset(f);
   except
       on e:exception do begin
       {workaround for WINE}
           CopyFile(errors,errors+'_WINEforced');
           assignfile(f,errors+'_WINEforced');
           filemode:=fmOpenRead;
           Reset(f);
       end;
   end;
   while(not EoF(f) ) and (counter<100) do begin      //too much errors do more harm than good
       ReadLn(f,line);
               //    MessageBox(0,pchar(line),'!',0);
       L:=Length(line);
       i:=1;
       seek(i);
       if(i<=L) then begin
           sect:=FixScriptFileName( Copy(line,1,i-1) );

           if(sect<>'parse successful') then begin
               k:=i+1;
               seek(k);
               if(k>L) then //it was an unrecognized character one, ignore
               else if( TryStrToInt(Copy(line,i+1,k-i-1),n) ) then begin

                   if(not maa) then begin
                       if(sect='blizzard.j') then begin
                           blizz:=true;
                       end
                       else if(sect='common.j') then begin
                           comm:=true;
                       end;
                   end;


                   if(not maa) then begin
                       if(comm) then jhErrors.StartErrors(commonj,JassHelperConfigFile.JASS_COMPILER+' - common.j')
                       else if(blizz) then jhErrors.StartErrors(blizzardj,JassHelperConfigFile.JASS_COMPILER+' - blizzard.j')
                       else jhErrors.StartErrors(war3mapj,JassHelperConfigFile.JASS_COMPILER);
                       maa:=true;
                   end;
                   counter:=counter+1;
                   jhErrors.AddErrors('maa???', n,Copy(line,k+1,L),true);
               end;
           end;
       end;
   end;


   Close(f);

   if(not maa) then begin
       raise Exception.Create('Unrecognized PJASS (syntax) error');
   end;
   jhErrors.ShowErrors();
end;

//==============================================================================
procedure doTool(const name:string; const prog:string;  args:string; const ext:string; const stdin:string);
var
   s,f, tem, tem2,tem3,tem4{,nargs}:string;
   ftem:Textfile;
   temi:integer;
begin

    s:='On external call:'#13#10+'//! external '+name+' '+args+#13#10#13#10;
    tem:=TempFile;
    tem2:=TempFile;
    tem3:=TempFile;
    tem4:=TempFIle;
    Assign(ftem,tem);
    Rewrite(ftem);
    Write(ftem,stdin);
    Close(ftem);
    CopyFile(tem,tem4);


    if (not FileExists(prog)) then begin
       raise Exception.Create(s+'Unable to find file: "'+prog+'"');
    end;
   //GetCurrentDir : much more portable than '.' , '.\' , '', nil , (towards WINE)
    if(ext<>'') then begin
        CopyFile(tem,tem+'.'+ext);
        args:=StringReplace(args,'$FILENAME$','"'+tem+'.'+ext+'"',[]);
    end;
    temi:= WinExec.StartApp(prog,'"'+map+'" "'+JasshelperConfigFile.WORK_PATHS+'" '+args,GetCurrentDir(),0, tem4,tem2,tem3);
    if(temi<>0) then begin
       tem4:=TempFile;
       CopyFile(tem2,tem4);
       f := LoadFile(tem4);
       s:=s+f;
       tem4:=TempFile;
       CopyFile(tem3,tem4);

       f := LoadFile(tem4);
       raise Exception.Create(s+f);
    end;
end;

//==============================================================================
procedure doExternalThings();
var
   i,j:integer;
   data:TExternalData;
begin
    {map holds map's path}

    //fixes some issues:
    for i := 1 to Length(WORK_PATHS) do if(WORK_PATHS[i]='\') then WORK_PATHS[i]:='/';

    if ExternalUsage.Count()>0 then begin
        for i := 0 to ExternalUsage.Count()-1 do begin
            j:=0;
            data := ExternalUsage.GetData(i);
            while(j<EXTERNAL_N) do begin
                if (EXTERNAL_Names[j]=data.name) then begin
                    ProStatus(data.name + data.args + ' : ');
                    doTool(EXTERNAL_Names[j],EXTERNAL_PROGRAMS[j],data.args, data.ext, data.stdin);
                    ProEnd(i/ExternalUsage.Count());
                    break;
                end;
                j:=j+1;
            end;
            if(j=EXTERNAL_N) then begin
                raise Exception.Create('External not found in config file: "'+data.name+'"');
            end;
        end;
    end;
end;

//==============================================================================
function jasshelperConfPlacements:TdynamicStringArray;
begin
    SeTLength(result,2);
    result[0]:='jasshelper.conf';
    result[1]:=JASSHELPER_PATH+'jasshelper.conf';
end;

//==============================================================================
procedure ParseArguments();
begin
  temi:=1;
  while(true) do begin
      if(paramStr(temi)='--about') then begin
          DoAbout();
          Halt;
      end else if(paramStr(temi)='--showerrors') then begin
          jhErrors.LoadErrors();
          Halt;
      end else if(ParamStr(temi)='--debug') then begin
          TurnOn_DEBUG_MODE();
          temi:=temi+1;
      end else if(ParamStr(temi)='--nopreprocessor') then begin
          // disable vJass syntax
          TurnOn_NOPRE_MODE();
          temi:=temi+1;
      end else if(ParamStr(temi)='--nooptimize') then begin
          // disable script optimization, only inliner atm.
          TurnOn_NOOPT_MODE();
          temi:=temi+1;
      end else if(ParamStr(temi)='--scriptonly') then begin
          TurnOn_SCRIPT_MODE();
          temi:=temi+1;
      end else if(ParamStr(temi)='--zinconly') then begin
          TurnOn_ZINC_MODE();
          temi:=temi+1;
      end else if(ParamStr(temi)='--warcity') then begin
          TurnOn_WARCITY_MODE();
          temi:=temi+1;
      end else if(ParamStr(temi)='--macromode') then begin
          TurnOn_MACRO_MODE();  
          temi:=temi+1;
      end else break;

  end;
end;

//==============================================================================
procedure InitFromArguments();
begin
try
  if (not DirectoryExists('logs')) then CreateDirectory('logs',nil);
  JASSHELPER_PATH := ExtractFileDir(paramstr(0));
  if(JASSHELPER_PATH[Length(JASSHELPER_PATH)]<>'\') then JASSHELPER_PATH:=JASSHELPER_PATH+'\';

  ParseArguments();

  TerminateAll(ExtractFileName(paramstr(0)));
  jhErrors.ClearErrors();

  mpq:=0;

  commonj:=ParamStr(temi);
  blizzardj:=ParamStr(temi+1);
  map:=ParamStr(temi+2);
  war3mapj:='';

  if(ParamStr(temi+3)<>'') then begin
    map:=ParamStr(temi+3);
    war3mapj:=ParamStr(temi+2);
  end;

    if(SCRIPT_MODE and (war3mapj='')) then raise Exception.Create('jasshelper.exe --scriptmode <common.j> <blizzard.j> <input.j> <output.j>');


    if (commonj='') then raise Exception.Create('Missing arguments: <commonj> <blizzardj> <mappath>');
    if (blizzardj='') then raise Exception.Create('Missing arguments: <blizzardj> <mappath>');
    if (map='') then raise Exception.Create('Missing argument: <mappath>');

    if (not FileExists(commonj)) then raise Exception.Create('File does not exist : '+commonj);
    if (not FileExists(blizzardj)) then raise Exception.Create('File does not exist : '+blizzardj);
    if (war3mapj<>'') and (not FileExists(war3mapj)) then raise Exception.Create('File does not exist : '+war3mapj);
    if (not SCRIPT_MODE) and (not FileExists(map)) then raise Exception.Create('File does not exist : '+map);

    SetCommonJAndBlizzardJ(commonj, blizzardj);

    f1:=tempfile;
    f2:=tempfile;
    f3:=tempfile;

  except on e:Exception do begin
     jhErrors.HandleException('Initialization Error', e);
  end;
  end;
end;

//==============================================================================
procedure LoadConfigs();
begin
  try
        if(SCRIPT_MODE) then begin
            folder:=ExtractFileDir(war3mapj);
        end
        else begin
            folder:=ExtractFileDir(map);
        end;
        if( (Length(folder)>0) and   (folder[Length(folder)]<>'\')) then folder:=folder+'\';

        importPathsClear();
        WORK_PATHS:=folder;
        addImportPath(folder); //ensures map's path as an import search path
        readConfig(jasshelperConfPlacements);
        //AUTOMETHODEVALUATE:= jasshelperConfigFile.AUTO_METHOD_EVALUATE;
  except on e:Exception do begin
     jhErrors.HandleException('LoadConfigs Error', e);
  end;
  end;
end;

//==============================================================================
//  after this stage script logs\inputwar3map.j is guaranteed to exist
//==============================================================================
function GetSource():string;
begin
  try
     if (not FileExists(JASSHELPER_PATH+'sfmpq.dll')) then raise Exception.CReate('Unable to find '+JASSHELPER_PATH+'sfmpq.dll');
     ProStatus('Loading (sfmpq.dll): OK');

     if(not SCRIPT_MODE) then
         mpq:=MpqOpenArchiveForUpdate(pchar(map),MOAU_OPEN_EXISTING + MOAU_MAINTAIN_LISTFILE,0);

     if(war3mapj='') then begin
         war3mapj:=INPUT_J;
         if(not MPQFileExists(mpq,'war3map.j')) then raise Exception.Create('Map does not contain war3map.j');
         DeleteFile(pchar(war3mapj));
         storm.MPQExtractFileTo(mpq,'war3map.j',pchar(war3mapj));
         if(not FileExists(war3mapj)) then raise Exception.Create('war3map.j extract error');
         ProStatus('Extracting (war3map.j): OK');
     end else begin
         CopyFile(war3mapj,INPUT_J);
         war3mapj:=INPUT_J;
     end;
     GetSource := LoadFile(INPUT_J);
  except on e:Exception do begin
     jhErrors.HandleException('GetSource Error', e);
  end;
  end;
end;

//==============================================================================
//  DISABLED - General purpose preprocessor
//==============================================================================
{*
procedure ProcessCPP(const inputFile:string; const outputFile:string);
var temi: integer;
    args: string;
    s, f, tem2, tem4, tem3: string;
    CPP_EXE: string;
begin
  try
    CPP_EXE := JASSHELPER_PATH + 'filepp\filepp.exe';
    ProStatus('C-PREPROCESSING: ');
    if (not FileExists(CPP_EXE)) then begin
      raise Exception.Create('ERROR: Unable to find C-Preprocessor: "'+CPP_EXE+'"');
    end;

    // save comments, readable comment output, no line info
    args := '-m grab.pm -m foreach.pm -m bigdef.pm ' + inputFile + ' -o' + outputFile + ' ';
    tem2:=TempFile;
    tem3:=TempFile;
    tem4:=TempFIle;
    temi:= WinExec.StartApp(CPP_EXE,args,GetCurrentDir(),0, tem4,tem2,tem3);
    if(temi<>0) then begin
       tem4:=TempFile;
       CopyFile(tem2,tem4);
       f := LoadFile(tem4);
       s:=s+f;
       tem4:=TempFile;
       CopyFile(tem3,tem4);

       f := LoadFile(tem4);
       raise Exception.Create(s+f);
    end;

    ProEnd();
  except on e:Exception do begin
    jhErrors.HandleException('C Preprocessor Error', e);
  end;
  end;
end;
*}

//==============================================================================
procedure PreprocessingAndStructs(var source:string);
begin
         stage:=1;
         source := Preprocessor.Run(source);
         stage:=2;
         if not EXCLUSIVE_MODE then begin
             source := Structs.Run(source);
             stage:=3;

             //if(REQUIREFOUND<>0) then begin
                 //cUnit.DoRequire();
                 //raise Exception.create('//! require of WEWarlock is no longer supported!');
             //end else begin
                 //CopyFile(compiled,'logs\currentmapscript.j');
             //end;
         end;
end;

//==============================================================================
procedure PJassCompiling(const source:string);
begin
      if (not FileExists(JASSHELPER_PATH+JasshelperConfigFile.JASS_COMPILER)) then raise Exception.Create('Unable to find: '+JASSHELPER_PATH+JasshelperConfigFile.JASS_COMPILER);

      if DEBUG_MODE() then begin
        ProStatus('  Debug Compiling: ');
      end else begin
        ProStatus('COMPILING: ');
      end;

      SaveFile(OUTPUT_J, source);
      //GetCurrentDir : much more portable than '.' , '.\' , '', nil , (towards WINE)
      temi:= WinExec.StartApp(
       JASSHELPER_PATH+JassHelperConfigFile.JASS_COMPILER,
       JassHelperConfigFile.ParserCommandLine('"'+commonj+'"', '"'+blizzardj+'"', OUTPUT_J),
       GetCurrentDir,0,f3,'logs\pjass.txt',f2);

      if(temi<>0) then begin
          dopjasserrors(OUTPUT_J,'logs\pjass.txt');
          halt(1);
      end;
      ProEnd();
end;

//==============================================================================
procedure RemoveAttributesInMPQ();
begin
    if(not SCRIPT_MODE) and (storm.MPQFileExists(mpq,'(attributes)')) then begin
        ProStatus('Removing (attributes): ');
        if not storm.MpqDeleteFile(mpq,'(attributes)') then begin
            raise Exception.Create('Unable to remove (attributes)');
        end;
        ProEnd();
    end;
end;

//==============================================================================
procedure SaveScript(const source:string);
begin
    ProStatus('Saving ('+OUTPUT_J+'): ');
    CopyFile(OUTPUT_J,source);
    if( not(NOPRE_MODE) and (ExternalUsage.Count()>0)) then begin
        raise Exception.Create('The script compilation was succesful, but //! external was found in the map, --scriptmode is incompatible with external tools');
    end;
    ProEnd();
end;

//==============================================================================
procedure SaveMap(const source:string);
begin
    ProStatus('Saving (war3map.j): ');
  CopyFile(OUTPUT_J,source);
    storm.MpqAddFileToArchiveEx(mpq,pchar(OUTPUT_J),'war3map.j',MAFA_REPLACE_EXISTING+MAFA_COMPRESS,MAFA_COMPRESS_DEFLATE,Z_BEST_COMPRESSION);
    storm.MpqCompactArchive(mpq);
    storm.MpqCloseUpdatedArchive(mpq,0);
    ProEnd();
    mpq:=0;
end;

//==============================================================================
const H1i = 'JASSHelper - Step 1 (textmacros, libraries, scopes) (scripts were imported)';
const H1  = 'JASSHelper - Step 1 (textmacros, libraries, scopes)';
const H2  = 'JASSHelper - Step 2 (structs)';

//==============================================================================
procedure HandleJASSException(e:TJASSerException; const source:string);
begin
     ProStatus('JASS Exception, please wait...');

     if(PreprocessorImport.wasUsed()) then begin
         SaveFile(OUTPUT_J, PreprocessorImport.GetOutput());
         jhErrors.StartErrors(OUTPUT_J,H1i)
     end else begin
         SaveFile(OUTPUT_J, source);
         if(stage=1) then jhErrors.StartErrors(OUTPUT_J,H1)
         else if(stage=2) then jhErrors.StartErrors(OUTPUT_J,H2);
     end;

     jhErrors.AddErrors(e.block, e.linen+1,e.msg,true);

     if(e.two) then begin
        jhErrors.AddErrors(e.block, e.linen2+1,e.msg2,false);
     end;

     if(e.macro1>=0) then jhErrors.AddErrors(e.block, e.macro1+1,'(From this instance)',false);
     if(e.macro2>=0) and(e.macro2<>e.macro1) then jhErrors.AddErrors(e.block, e.macro2+1,'(From this instance)',false);

     jhErrors.ShowErrors;
     Halt(1);
end;

//==============================================================================
//  Execution algorithm of all jasshelper parts.
//==============================================================================
procedure Jasshelper(var source: string);
  label RESTARTLABEL;
begin
  // EVIL
  RESTARTLABEL:

  source := GetSource();
  //ProcessCPP('logs\inputwar3map.j', 'logs\currentmapscript.j');
  //CopyFile('logs\inputwar3map.j', 'logs\currentmapscript.j');

    if(not NOPRE_MODE) then begin
          PreprocessingAndStructs(source);
      if DEBUG_MODE() then PJassCompiling(source);
    end;

    if not EXCLUSIVE_MODE then begin
      if (jasshelperconfigfile.ENABLE_RETURN_FIXER) then begin
          source := ReturnFix.Run(source);
      if DEBUG_MODE() then PJassCompiling(source);
    end;
      if (jasshelperconfigfile.ENABLE_SHADOW_HELPER) then begin
      source := ShadowHelper.Run(source);
      if DEBUG_MODE() then PJassCompiling(source);
    end;
        if not NOOPT_MODE then begin
            source :=    Inliner.Run(source);
      if DEBUG_MODE() then PJassCompiling(source);
        end;
    end;

  if not DEBUG_MODE() then PJassCompiling(source);

  RemoveAttributesInMPQ();

    if SCRIPT_MODE then begin
        SaveScript(source);
    end else begin
        SaveMap(source);
        if (not NOPRE_MODE) and (ExternalUsage.Count()>0) then begin
            ProRelease();
            ProInit('External Tools');
            doExternalThings();
            war3mapj:='';
            usedExternal := true;

            ProRelease();
            ProInit('JASSHelper');
            goto RESTARTLABEL; //Dijkstra would be so dissapointed
        end;
    end;
  ProStatus('ALL OK.');
  ProRelease();
end;

//==============================================================================
//  SUBMAIN - try block wrapper for Jasshelper
//==============================================================================
procedure SubMain();
var source : string;
begin
  try
    InitFromArguments();
    ProInit('JASSHelper');  // do not put this before InitFromArguments
    takeBackup(map);
    LoadConfigs();

    Jasshelper(source); // <--------<<

    if (mpq<>0) then begin
        MpqCloseUpdatedArchive(mpq,0);
    end;
  except
    on e:TJASSerException do begin
        HandleJassException(e, source);
    end;
  end;
end;


//==============================================================================
//  MAIN - try finnaly wrapper for mpq
//==============================================================================
procedure Main();
begin
    try
        SubMain();
    finally
        // make sure we close the map archive, so file does not lock.
        if (mpq<>0) then begin
            MpqCloseUpdatedArchive(mpq,0);
        end;
    end;
end;  // end of main

end.  // end of unit



