IMPLEMENTATION MODULE Config;

FROM Conversions IMPORT IntConversions;
IMPORT String;
IMPORT SYSTEM;
FROM Linked IMPORT StringLinkable;

  CLASS Parameter;
    VAR
      TheVar, TheVal: ARRAY OF CHAR;

    REDEFINE METHOD CREATE(Source: ARRAY OF CHAR);
      VAR
        i: INTEGER;
      BEGIN
      i := String.Pos (Source, '=');
      IF (i < Source.SIZE) AND (i > 0) THEN
        TheVar := String.Strip(String.Left(Source, i));
        TheVal := String.Strip(String.Right(Source, Source.SIZE - i - 1));
        IF TheVal <> VOID THEN
          TheVal := String.ParseEscapeSequence (TheVal);
          END;
        END;
      END CREATE;

    METHOD Variable: ARRAY OF CHAR;
      BEGIN
      RESULT := TheVar;
      END Variable;

    METHOD Value: ARRAY OF CHAR;
      BEGIN
      RESULT := TheVal;
      END Value;
      
    METHOD SaveStream (Output: OutputStream);
      BEGIN
      Output.WriteLine (Variable + "=" + Value);
      END SaveStream;

    END Parameter;
----------------------------------
  CLASS Configuration;
    VAR
      PList: List(Parameter);
      AnErrorCode: INTEGER;
      ALinked: Configuration;
      
    REDEFINE METHOD CREATE;
      BEGIN
      PList.CREATE;
      AnErrorCode := NoError;
      END CREATE; 
                                         
    METHOD ErrorCode: INTEGER;
      BEGIN         
      RESULT := AnErrorCode;
      END ErrorCode;  
      
    METHOD EqualString (Label, ParamKey: ARRAY OF CHAR): BOOLEAN;
      BEGIN
      RESULT := String.Equals (Label, ParamKey);
      END EqualString;

    METHOD FindValue (Label: ARRAY OF CHAR): ARRAY OF CHAR;
      VAR
        Param: Parameter;
      BEGIN
      Param := FindParameter (Label);
      IF Param <> VOID THEN
        RESULT := Param.Value;
        END;
      END FindValue;
      
    METHOD FindParameterPos (Label: ARRAY OF CHAR): INTEGER;
      BEGIN
      RESULT := -1;
      FOR i := PList.Size -1 TO 0 BY -1 WHILE RESULT = -1 DO
        IF EqualString (Label, PList.Get(i).Variable) THEN
          RESULT := i;
          END;
        END;
      END FindParameterPos;
      
    METHOD FindParameter (Label: ARRAY OF CHAR): Parameter;
      VAR
        i: INTEGER;
      BEGIN
      i := FindParameterPos (Label);
      IF i >= 0 THEN
        RESULT := PList.Get(i);
       ELSIF ALinked <> VOID THEN
        RESULT := ALinked.FindParameter (Label);
        END;
      END FindParameter;

    METHOD FindMultiLineValue (Label: ARRAY OF CHAR): ARRAY OF ARRAY OF CHAR;
      VAR
        a, b: ARRAY OF CHAR;
        i, Used: INTEGER;
        GoOn: BOOLEAN;
      BEGIN
      a := FindValue (Label);
      IF a <> VOID THEN
        RESULT.CREATE(1);
        RESULT[0] := a;
       ELSE
        GoOn := TRUE;
        WHILE GoOn DO
          b := Label + '.' + IntConversions.IntToString(i,0);
          a := FindValue (b);
          IF a <> VOID THEN
            IF RESULT = VOID THEN
              RESULT.CREATE (4);
              END;
            IF Used = RESULT.SIZE THEN
              RESULT := RESULT + RESULT;
              END;
            RESULT[Used] := a;
            Used := Used + 1;
           ELSE
            GoOn := i < 2;
            END;
          i := i + 1;
          END;
        IF (RESULT <> VOID) AND (RESULT.SIZE > Used) THEN
          RESULT := RESULT.SLICE (0, Used);
          END;
        END;
      END FindMultiLineValue;

    METHOD ParamList: List(Parameter);
      BEGIN
      RESULT := PList;
      END ParamList;

    METHOD EnterLine(Line: ARRAY OF CHAR);
      VAR
        a: ARRAY OF CHAR;
        p: Parameter;
      BEGIN
      a := String.Strip(Line);
      IF (a <> VOID) AND (a.SIZE > 0) AND (a[0] <> ";") AND (a[0] <> '#') 
                 THEN
        p.CREATE(a);
        IF p.Variable <> VOID THEN
          PList.Append (p);
          END;
        END;
      END EnterLine;

    METHOD UseStream(Input: InputStream);
      VAR
        Line: ARRAY OF CHAR;
      BEGIN
      WHILE NOT Input.Eof DO
        Line := Input.ReadLine;
        WHILE NOT Input.Eof AND (Line.SIZE > 2) AND 
                                (Line[Line.SIZE - 1] = '\') AND
                                (Line[Line.SIZE - 2] <> '\') DO
          Line := Line.SLICE(0, Line.SIZE - 1) + Input.ReadLine;
          END;                          
        EnterLine(Line);
        END;
      END UseStream;

    VAR
      AltPath: ARRAY OF CHAR;

    METHOD UseAlternatePath(Path: ARRAY OF CHAR);
      BEGIN
      AltPath := Path;
      ASSERT AltPath <> VOID;
      ASSERT AltPath.SIZE > 0;
      IF AltPath [AltPath.SIZE - 1] <> SYSTEM.DirSepChar THEN
        AltPath := AltPath + SYSTEM.DirSeparator;
        END;
      END UseAlternatePath;

    METHOD UseFile(FName: ARRAY OF CHAR);
      VAR
        Input: InputStream;
      BEGIN
      Input.CREATE;
      Input.Open (FName, Stream.ReadAccess);
      AnErrorCode := Input.ErrorCode;
      IF AnErrorCode = Stream.NoError THEN
        UseStream (Input);
        Input.Close;
       ELSIF AltPath <> VOID THEN
        Input.Open (AltPath + FName, Stream.ReadAccess);
        AnErrorCode := Input.ErrorCode;
        IF AnErrorCode = Stream.NoError THEN
          UseStream (Input);
          Input.Close; 
          END;
        END;   
      END UseFile;
      
    METHOD SaveStream (Output: OutputStream);
      BEGIN
      FOR i := 0 TO PList.Size - 1 DO
        PList.Get(i).SaveStream (Output);
        END;
      END SaveStream;
      
    METHOD SaveFile (FName: ARRAY OF CHAR);
      VAR
        Output: OutputStream;
      BEGIN
      Output.CREATE;
      Output.Create (FName, Stream.WriteAccess);
      IF Output.ErrorCode = Stream.NoError THEN
        SaveStream (Output);
        Output.Close;
       ELSE
        AnErrorCode := Output.ErrorCode;	
        END; -- IF
      END SaveFile;
      
    METHOD SetParameter (Label, Value: ARRAY OF CHAR);
      VAR
        i: INTEGER;
        p: Parameter;
      BEGIN
      i := FindParameterPos (Label);
      IF i >= 0 THEN
        PList.Delete (i);
        END;
      p.CREATE (Label + "=" + Value);      
      PList.Append (p);
      END SetParameter;

      METHOD SubstituteVariables (Source: ARRAY OF CHAR): ARRAY OF CHAR;
        CONST
          Dollar = '$';
        VAR
          Target: ARRAY OF CHAR;
          TargetPos, i: INTEGER;
          
          METHOD HandleVariable (Source: ARRAY OF CHAR;
                                 StartPos: INTEGER): INTEGER;
            VAR
              Start, End: INTEGER;                                 
              a: ARRAY OF CHAR;
              Ch: CHAR;
              BEGIN
            RESULT := Source.SIZE;
            ASSERT Source[StartPos] = Dollar;
            Start := StartPos + 1;
            IF Start < Source.SIZE THEN
              IF Source[Start] = '(' THEN
                Start := Start + 1;
                End := Start;
                WHILE (End < Source.SIZE) AND (Source[End] <> ')') DO
                  End := End + 1;
                  END;
                IF End < Source.SIZE THEN
                  ASSERT Source[End] = ')';
                  RESULT := End;
                  a := Source.SLICE (Start, End-Start);
                  END;
               ELSE
                ---------------------------------------
                -- The variable name is not enclosed within
                -- parentheses. Let's then perform a simple 
                -- lexical analysis...
                ---------------------------------------
                End := Start;
                Ch := SYSTEM.UCASE(Source[End]);
                WHILE (End < Source.SIZE) AND 
                      (((Ch >= 'A') AND (Ch <= 'Z')) OR
                       ((Ch >= '0') AND (Ch <= '9')) OR
                       (Ch = '_')) DO
                  End := End + 1;
                  IF End < Source.SIZE THEN
                    Ch := SYSTEM.UCASE(Source[End]);
                   ELSE
                    Ch := ' ';
                    END;
                  END;
                a := Source.SLICE (Start, End-Start);
                RESULT := End - 1;
                END;
              END;                        
            IF a <> VOID THEN 
              a := FindValue (a);
              IF a <> VOID THEN
                FOR i := 0 TO a.SIZE - 1 DO
                  Target[TargetPos] := a[i];
                  TargetPos := TargetPos + 1;
                  END;
                END;
              END;
            END HandleVariable;
            
        BEGIN                  
        IF Source <> VOID THEN
          i := String.Pos (Source, Dollar);
          ---------------------------------------
          -- If there is at least a single '$' sign, check whether
          -- variable substitution ought to be performed.
          ---------------------------------------
          IF i >= Source.SIZE THEN
            RESULT := Source;
           ELSE
            Target.CREATE (512);            
            i := 0;
            WHILE i < Source.SIZE DO
              CASE Source[i] OF
                '\':
                  Target[TargetPos] := Source[i+1];
                  TargetPos := TargetPos + 1;
                  i := i + 1;
                  END;
                '$':
                  i := HandleVariable (Source, i);
                  END;
               ELSE
                Target[TargetPos] := Source[i];
                TargetPos := TargetPos + 1;
                END;
              i := i + 1;
              END;
            RESULT := Target.SLICE (0, TargetPos);
            END;
          END;
        END SubstituteVariables;

        
    METHOD Link (Other: Configuration);
      VAR
        p: Configuration;
        Ok: BOOLEAN;
      BEGIN
      Ok := TRUE;
      p := Other;
      WHILE Ok AND (p <> VOID) DO
        Ok := p <> THIS;
        p := p.ALinked;
        END;
      IF Ok THEN
        ALinked := Other;
        END;
      END Link;

    METHOD Unlink;
      BEGIN
      ALinked := VOID;
      END Unlink;

    METHOD Linked: Configuration;
      BEGIN
      RESULT := ALinked;
      END Linked;

  END Configuration;

END Config;
