//==============================================================================
//   processes import directives, the result is stored in JHInput.input array
//==============================================================================
unit PreprocessorImport;

//==============================================================================
//  INTERFACE
//==============================================================================
interface

procedure Run(const sinput:string);

function GetOutput():string;
function wasUsed():boolean;
procedure ResetFlag();

//==============================================================================
//  IMPLEMENTATION
//==============================================================================
implementation
uses
    TextOnly,
    TextCommon,
    StringHash,
    vxFilesys,
    PseudoRandom,
    JASSerException,
    JHInterface,
    JHInput,
    JHParams,
    TempUnit,
    ImportPaths;

//==============================================================================
var
    STATE: record
        pendingline : boolean;
        comment : integer;
        localzinc:integer;
        zinc:boolean;
    end;

    // big string, in case of need for premature output
    AFTERIMPORT:string;

    // did we actually found any import directives?
    IMPORTUSED:boolean;

//==============================================================================
procedure importPhase(const sinput: string); forward;

//==============================================================================
procedure addFileLine (const line:string; ignorecom:boolean = false);
begin
    if(STATE.pendingline) then begin
        JHInput.AppendToLastLine(line)
    end else begin
        JHInput.AddLine1(line);
    end;
    if( not(ignorecom) and (STATE.comment>0) ) then begin
        Swrite(AFTERIMPORT, line);
        STATE.pendingline:=true;
    end else begin
        SWriteLn(AFTERIMPORT, line);
        STATE.pendingline:=false;
    end;
end;

//==============================================================================
//  removes multiline comments from line
//==============================================================================
function dealWithComments(const line:string):string;
var
    i,k,L:integer;
    modify: boolean;
    str:boolean;
    ctr:boolean;
begin
    modify:=( STATE.comment > 0);
    L:=Length(line);
    if(not modify) then
        for i := 1 to L-1 do begin
            if(line[i]='/') and (line[i+1]='/') then
               break
            else if (line[i]='/') and (line[i+1]='*') then begin
               IMPORTUSED:=TRUE;
               modify:=true;
               break;
            end;
        end;
    if(not modify) then begin
        Result:=line;
        exit;
    end;
    // we get here only if line contains multiline comment opening /*
    k:=0;
    i:=1;
    str:=false;
    ctr:=false;
    while(i<=L) do begin
        if(STATE.comment>0) then begin
            if(line[i] = '*') and (i<L) and (line[i+1]='/') then begin
                i:=i+1;
                STATE.comment := STATE.comment - 1;
            end else if (line[i] = '/') and (i<L) and (line[i+1]='*') then begin
                i:=i+1;
                STATE.comment:= STATE.comment +1;
                IMPORTUSED:=TRUE;
            end;
            k:=i;
        end else if(str) then begin
            if(ctr) then
                ctr:=false
            else if(line[i]='\') then
                ctr:=true
            else if(line[i]='"') then
                str:=false;
        end else begin
            if(line[i] = '/') and (i<L) and (line[i+1]='/') then
                break
            else if(line[i] = '/') and (i<L) and (line[i+1]='*') then begin
                IMPORTUSED:=TRUE;
                STATE.comment := STATE.comment + 1;
                Result:=Result + Copy(line,k+1, i-k-1);
                i:=i+1;
            end else if(line[i] = '"') then str:=true;
        end;
        i:=i+1;
    end;
    if(STATE.comment =0) then Result:=Result + Copy(line,k+1,L-k);
end;

//==============================================================================
//  Actually imports a file specified on //! import line
//==============================================================================
procedure doImport(const line: string; start:integer);
var 
    i, a,b, L:integer;
    filename, shortfilename : string;
    giantString: string;

    usedMode:integer;
    renableZinc:integer;
    llocalzinc:integer;

    hash:TStringHash;

    label finish;
begin
    hash:=TStringHash.Create;

    llocalzinc:=STATE.localzinc;
    IMPORTUSED:=TRUE;

    L:=length(Line);
    //0:none, 1:vJass, 2:zinc
    if(CompareLineWord('vjass',line,i, start) ) then begin
        usedMode:=1;
    end else if(CompareLineWord('zinc',line,i, start) ) then begin
        usedMode:=2;
    end else if(CompareLineWord('comment', line,i,start) and not WARCITY_MODE ) then begin
        AddFileLine('// IGNORE COMMENT IMPORT OF '+filename);
        goto finish; //Dijkstra forgive me!
    end else begin
        if(STATE.zinc) then
            usedMode:=2
        else
            usedMode:=1;
        i:=start;
    end;
    STATE.localzinc:=-1;


    while ((i<=L) and (line[i]<>'"') ) do i:=i+1;
    a:=i;
    if( a > L) then begin
        addFileLine(line);
        raise JASSerLineExceptionNoTextMacro('import requires filename between quotes. (")');
    end;
    i:=i+1;
    while ((i<=L) and (line[i]<>'"') ) do i:=i+1;
    b:=i;
    if( b > L) then begin
        addFileLine(line);
        raise JASSerLineExceptionNoTextMacro('Expected: "');
    end;
    filename := Copy(line, a+1, b-a-1);
    if(not fetchPath(filename) ) then begin
        addFileLine(line);
        raise JASSerLineExceptionNoTextMacro('Unable to find file: '+filename);
    end;
    addImportPath(CrossExtractFolder(filename));
    shortfilename := CrossNormalizeFilename(filename);
    if( hash.ValueOf(shortfilename) <> -1) then begin
        if(not WARCITY_MODE ) then begin
            AddFileLine('// IGNORE DOUBLE IMPORT OF '+filename);
        end;
    end else begin
        hash.Add(shortfilename,1);
        renablezinc:=0;
        if( STATE.zinc and (usedmode=1) ) then begin
            STATE.zinc:=false;
            renablezinc:=1;
            AddFileLine('//! endzinc');
        end else if( not(STATE.zinc) and (usedmode=2) ) then begin
            STATE.zinc:=true;
            renablezinc:=-1;
            AddFileLine('//! zinc');
        end;

        if(not WARCITY_MODE ) then begin
            AddFileLine('// BEGIN IMPORT OF '+filename);
        end;
        giantString := LoadFile(filename);
        importPhase( giantString);   // recursion yay
        if(not WARCITY_MODE ) then begin
            AddFileLine('// END IMPORT OF '+filename);
        end;

        if(renablezinc=1) then begin
            STATE.zinc:=true;
            AddFileLine('//! zinc');
        end else if(renablezinc=-1) then begin
            STATE.zinc:=false;
            AddFileLine('//! endzinc');
        end;

    end;
    finish:
    if(STATE.localzinc<>-1) then begin
        raise JASSerLineExceptionNoTextMacro(STATE.localzinc,'Unterminated //! zinc in file: '+filename);
    end;
    STATE.localzinc:=llocalzinc;

    hash.Destroy;
end;

//==============================================================================
//  process //! import, //! zinc and //! vjass directives
//==============================================================================
procedure parseFileLine(const cline: string; var novjass:integer);
var
    i : integer;
    word, line:string;
    addline: boolean;
begin
    // just randomizing for every line :)
    GetPseudoRandom();

    line := dealWithComments(cline);
    addline:= true;
    if CompareLineWord('//!',line,i) then begin
        GetLineWord(line, word, i,i);
        if (not ZINC_MODE) and (novjass=0) and (word='import') then begin
            addline := false;
            doImport(line, i);
        end else if (word='zinc') then begin
            if(STATE.zinc) then begin
                addFileLine(line);
                raise JASSerLineExceptionNoTextMacro('Nested //! zinc');
            end;
            STATE.localzinc:=inputN;
            STATE.zinc := true;
        end else if (word='endzinc') then begin
            if(STATE.localzinc=-1) then begin
                addFileLine(line);
                raise JASSerLineExceptionNoTextMacro('Unexpected: //! endzinc');
            end;
            STATE.zinc := false;
            STATE.localzinc:=-1;
        end else if (word='novjass') then begin
            IMPORTUSED:=true;
            novjass:=novjass + 1;
        end else if (word='endnovjass') then begin
            if(novjass<=0) then begin
                addFileLine(line);
                raise JASSerLineExceptionNoTextMacro('//! endnovjass found with no corresponding //! novjass');
            end;
            novjass:=novjass-1;
            addline:=false;
        end;
    end;

    if addline and (novjass = 0) then begin
        if(WARCITY_MODE) then
            addFileLine(cline, true)
        else
            addFileLine(line, false);
    end;
end;

//==============================================================================
// breaks input string into lines and calles parseFileLine
//==============================================================================
procedure importPhase(const sinput: string);
var
    k,i, L:integer;
    line:string;
    novjass : integer;
begin
    ProStatus('  Importing: ');
    L:=Length(sinput);
    //SetLength(input, Length(input) + 1 + L div 50);
    STATE.comment:=0;
    novjass:=0;
    k:=0;
    i:=1;
    STATE.pendingline:=false;

    while(i<=L+1) do begin
        if (i>L)
        or (sinput[i]=#10)
        or ( (sinput[i]=#13) and ((i+1>L) or (sinput[i+1]<>#10)) )
        then begin //line break.
            if( (i>1) and (sinput[i-1] = #13) ) then begin
                line := Copy(sinput,k+1, i-k-2);
            end else begin
                line := Copy(sinput,k+1, i-k-1);
            end;
            parseFileLine(line, novjass);
            k:=i;
        end;
        i:=i+1;
    end;
    
    if(novjass>0) then
    raise JASSerLineExceptionNoTextMacro('The file contains unclosed //! novjass statements');

    if (STATE.comment>0) then
    raise JASSerLineExceptionNoTextMacro('The file contains unclosed /* comments!');

    ProEnd();
end;

//==============================================================================
function GetOutput():string;
begin
    Result := AFTERIMPORT;
end;

//==============================================================================
function wasUsed():boolean;
begin
    Result := IMPORTUSED;
end;

//==============================================================================
procedure ResetFlag();
begin
    IMPORTUSED := false;
end;

//==============================================================================
//  Main function for this unit.
//==============================================================================
procedure Run(const sinput:string);
begin
    STATE.zinc:=false;
    STATE.localzinc:=-1;
    AFTERIMPORT:='';
    IMPORTUSED:=false;
    importPhase(sinput);
end;

end.
