(* ini-file.sml *)

structure IniFile =
struct
    
(*
  structure RE = RegExpFn (structure P = AwkSyntax
                           structure E = DfaEngine)

  val version_re = RE.compileString ("%%version%%")
*)

  type binding = {name:string, value:string}
  type section = {name:string, bindings:binding list}
  type file = {sections:section list}
    
  (* check if the string is actual a section
   * header of the form [something...]
   *)
  fun isHeader (s) = (String.sub (s,0) = #"[")
    
  (* extract the section name from the section header
   *)
  fun getHeader (s) = let
    val t = hd (String.tokens (fn c => c = #"]") s)
  in
    String.extract (t,1,NONE)
  end                

  
  (* split a string at the = sign
   *)
  fun getBinding (s) = let
    val ss = Substring.all (s)
    val (s1,s2) = Substring.splitl 
      (fn #"=" => false | _ => true) ss
  in
    {name=Substring.string s1,
     value=Substring.string (Substring.triml 1 
                             (Substring.trimr 1 s2))}
  end

  
  fun readLine (stream) = let 
    val v = TextIO.inputLine (stream)
  in
    if (v="\n") 
      then readLine (stream)
    else v
  end

  (* 
   * Read a .ini file into a ini_file 
   * structure 
   *)
  fun read (file) = let 
    val stream = TextIO.openIn (file)
    val curr = ref ""
    fun sectionLoop () = 
      if (TextIO.endOfStream (stream))
        then [] before curr := ""
      else if (isHeader (!curr))
             then []
           else let 
             val v = !curr
           in
             curr := readLine (stream);
             getBinding (v)::sectionLoop ()
           end
    fun sectionStart () = 
      let 
        val section = !curr
      in
        curr := readLine (stream);
        {name=getHeader (section), bindings=sectionLoop ()}
      end
    fun loop () =
      let
        val v = sectionStart ()
      in
        if (!curr="")
          then [v]
        else 
          v :: loop()
      end
  in
    curr := readLine (stream);
    {sections=loop ()} before TextIO.closeIn (stream)
  end

(*
  fun replace (string, pos, len, new) = let
    val b = String.extract (string,0,SOME pos)
    val e = String.extract (string,pos+len,NONE)
  in
    concat [b,new,e]
  end

  fun filter_version (string,version) = let
    val scan = StringCvt.scanString (RE.find version_re )
  in
    case (scan string)
      of NONE => string
       | SOME (mt) => case (MatchTree.root (mt))
                        of NONE => raise Fail "No matching info!"
                          | SOME {pos,len} => replace (string,pos,len,version)
  end
*)
  
  val crlf = "\r\n"

  fun write (name,file:file) = let 
    val stream = TextIO.openOut (name)
    fun output (s) = TextIO.output (stream,s)
    fun outputBinding (binding:binding) = 
      (output (#name binding);
       output "=";
       output (#value binding);
       output crlf)
    fun outputSection (section:section) = 
      (output "[";
       output (#name section);
       output "]\r\n";
       app outputBinding (#bindings section);
       output crlf)
  in
    app outputSection (#sections file);
    TextIO.closeOut (stream)
  end                
  
  
  (* 
   * dump the code to create an ini_file structure
   *)
  fun dumpCode (out, name, file : file) = let
    fun appLast f [] = ()
      | appLast f [x] = f (x,true)
      | appLast f (x::xs) = (f (x,false); appLast f xs)
    fun output (s) = TextIO.output (out,s)
    fun outputQuoted (s) = (output "\""; 
                            output s;
                            output "\"") 
    fun dumpBinding (binding : binding,isLast) =  
                            (output "        {name = ";
                             outputQuoted (#name binding);
                             output " ,";
                             output " value = ";
                             outputQuoted (#value binding);
                             output "}";
                             if (isLast) 
                               then output crlf
                             else output ",\r\n")
    fun dumpSection (section : section,isLast) = 
                            (output "     {name = ";
                             outputQuoted (#name section);
                             output ",\r\n";
                             output "      bindings = [\r\n";
                             appLast dumpBinding (#bindings section);
                             output "      ]}";
                             if (isLast)
                               then output crlf
                             else output ",\r\n")
  in 
    output "val ";
    output name;
    output " =\r\n";
    output "  {name = ";
    outputQuoted (name);
    output ",\r\n";
    output "   sections = [\r\n";
    appLast dumpSection (#sections file);
    output "   ]}\r\n\r\n"
  end                               

  fun rebind ({sections}: file, sect, bind, newval) = let
    fun rebindBinding (binding as {name, value}) = 
      if (name=bind)
        then {name=name,
              value=newval}
      else binding
    fun rebindSection (section as {name, bindings}) = 
      if (name=sect) 
        then {name=name,
              bindings=(map rebindBinding bindings)}
      else section
  in
    {sections=(map rebindSection sections)}
  end

end (* structure IniFile *)

