(********************************************************************)
(*                                                                  *)
(*  propertyfile.s7i  Read key-value pairs from a property file     *)
(*  Copyright (C) 2013  Thomas Mertes                               *)
(*                                                                  *)
(*  This file is part of the Seed7 Runtime Library.                 *)
(*                                                                  *)
(*  The Seed7 Runtime Library is free software; you can             *)
(*  redistribute it and/or modify it under the terms of the GNU     *)
(*  Lesser General Public License as published by the Free Software *)
(*  Foundation; either version 2.1 of the License, or (at your      *)
(*  option) any later version.                                      *)
(*                                                                  *)
(*  The Seed7 Runtime Library is distributed in the hope that it    *)
(*  will be useful, but WITHOUT ANY WARRANTY; without even the      *)
(*  implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR *)
(*  PURPOSE.  See the GNU Lesser General Public License for more    *)
(*  details.                                                        *)
(*                                                                  *)
(*  You should have received a copy of the GNU Lesser General       *)
(*  Public License along with this program; if not, write to the    *)
(*  Free Software Foundation, Inc., 51 Franklin Street,             *)
(*  Fifth Floor, Boston, MA  02110-1301, USA.                       *)
(*                                                                  *)
(********************************************************************)


include "scanfile.s7i";
include "utf8.s7i";

const set of char: propertyWhiteSpace is {' ', '\t', '\f'};


(**
 *  Map to store key-value pairs ([[hash]] [ [[string]] ] [[string]]).
 *)
const type: propertyDataType is hash [string] string;


const func string: readPropertyNameOrValue (inout file: inFile, in set of char: terminatorChars) is func
  result
    var string: nameOrValue is "";
  local
    var boolean: readNextChar is FALSE;
  begin
    while inFile.bufferChar <> EOF and
        inFile.bufferChar not in terminatorChars do
      if inFile.bufferChar = '\\' then
        inFile.bufferChar := getc(inFile);
        readNextChar := TRUE;
        case inFile.bufferChar of
          when {'t'}: nameOrValue &:= '\t';
          when {'n'}: nameOrValue &:= '\n';
          when {'f'}: nameOrValue &:= '\f';
          when {'r'}: nameOrValue &:= '\r';
          when {'u'}: nameOrValue &:= chr(integer(gets(inFile, 4), 16));
          when {'U'}: nameOrValue &:= chr(integer(gets(inFile, 6), 16));
          when {'\r'}: inFile.bufferChar := getc(inFile);
                       if inFile.bufferChar = '\n' then
                         inFile.bufferChar := getc(inFile);
                       end if;
                       skipWhiteSpace(inFile, propertyWhiteSpace);
                       readNextChar := FALSE;
          when {'\n'}: inFile.bufferChar := getc(inFile);
                       skipWhiteSpace(inFile, propertyWhiteSpace);
                       readNextChar := FALSE;
          otherwise: nameOrValue &:= inFile.bufferChar;
        end case;
        if readNextChar then
          inFile.bufferChar := getc(inFile);
        end if;
      else
        nameOrValue &:= inFile.bufferChar;
        inFile.bufferChar := getc(inFile);
      end if;
    end while;
  end func;


(**
 *  Reads from the open property file 'inFile' and returns its propertyDataType value.
 *)
const func propertyDataType: readPropertyFile (inout file: inFile) is func
  result
    var propertyDataType: propertyData is propertyDataType.value;
  local
    const set of char: keyTerminator is {'=', ':', ' ', '\t', '\f', '\r', '\n'};
    const set of char: valueTerminator is {'\r', '\n'};
    var string: propertyName is "";
    var string: propertyValue is "";
  begin
    inFile.bufferChar := getc(inFile);
    while inFile.bufferChar <> EOF do
      skipWhiteSpace(inFile, propertyWhiteSpace);
      case inFile.bufferChar of
        when {'#', '!'}:
          skipLineComment(inFile);
        when {'\r'}:
          inFile.bufferChar := getc(inFile);
          if inFile.bufferChar = '\n' then
            inFile.bufferChar := getc(inFile);
          end if;
        when {'\n'}:
          inFile.bufferChar := getc(inFile);
        when {EOF}:
          noop;
        otherwise:
          propertyName := readPropertyNameOrValue(inFile, keyTerminator);
          skipWhiteSpace(inFile, propertyWhiteSpace);
          if inFile.bufferChar = '=' or inFile.bufferChar = ':' then
            inFile.bufferChar := getc(inFile);
            skipWhiteSpace(inFile, propertyWhiteSpace);
          end if;
          propertyValue := readPropertyNameOrValue(inFile, valueTerminator);
          propertyData @:= [propertyName] propertyValue;
          # writeln("add: " <& propertyName <& "=" <& propertyValue);
      end case;
    end while;
  end func;


(**
 *  Read a property file with the given name.
 *)
const func propertyDataType: readPropertyFile (in string: fileName) is func
  result
    var propertyDataType: propertyData is propertyDataType.value;
  local
    var file: inFile is STD_NULL;
  begin
    inFile := open(fileName, "r");
    if inFile <> STD_NULL then
      propertyData := readPropertyFile(inFile);
      close(inFile);
    end if;
  end func;


(**
 *  Read an UTF-8 encoded property file with the given name.
 *)
const func propertyDataType: readPropertyFile8 (in string: fileName) is func
  result
    var propertyDataType: propertyData is propertyDataType.value;
  local
    var file: inFile is STD_NULL;
  begin
    inFile := openUtf8(fileName, "r");
    if inFile <> STD_NULL then
      propertyData := readPropertyFile(inFile);
      close(inFile);
    end if;
  end func;