/*
   ----------------------------------------------------------------
   Filename.....: rmmIni.cc
   ClassName....: rmmIni
   Programmer...: Rick Miller.
   Date.........: August 22, 2006.
   Purpose......: Non-GUI Object to read/write ini/text files.
   Notes........: See Notes below.
   Written for..: dBASE 32 bit.
   Rev. History.: August 25, 2008.
                  Added an optional <int> decimal parameter to
                  getFloat, getNumber, setFloat, setNumber methods.
                  Added a delete method to delete this.file.
   Dependencies.: None.
   Calls........: See x_InitializeExterns.
   Called by....: Any.
   Usage........: set procedure to rmmIni.cc additive
   Example......: See Example 1, 2 below.
   ----------------------------------------------------------------
   Properties:
   ----------------------------------------------------------------
      file  <char>   set("directory") + "\" + login name + ".ini"
   ----------------------------------------------------------------
   Example 1: standalone object.
   ----------------------------------------------------------------
         oIni  =  new rmmIni()

         Retreive a value from oIni.file.
         cVar  =  oIni.get("section", "entryname")

         Place a value into oIni.file.
         lVar  =  oIni.set("section", "entryname", value)

         oIni.release()
         oIni  := null
   ----------------------------------------------------------------
   Example 2: child of a form.
   ----------------------------------------------------------------
         form.ini =  new rmmIni()

         While the form is in scope:
         Retreive a value from form.ini.file.
         cVar  =  form.ini.get("section", "entryname")

         Place a value into form.ini.file.
         lVar  =  form.ini.set("section", "entryname", value)

         In the form.canClose or form.onClose event.
         form.ini.release()
         form.ini := null
   ----------------------------------------------------------------
   Methods:
   ----------------------------------------------------------------
      delete()    delete this.file.
         return   <logical>.
      flush()     flush writes to this.file in the cache.
         return   <logical>.
      get(<char> section, <char> entry)
         return   <char>.
      getFloat(<char> section, <char> entryname[, <int> decimals])
         return   <float>.
      getInt(<char> section, <char> entryname)
         return   <int>.
      getNumber(<char> section, <char> entryname[, <int> decimals])
         return   <number>.
      release()
         destroy object.
         close procedure.
      set(<char> section, <char> entryname, value)
         return   <logical>.
      setFloat(<char> section, <char> entryname, value[, <int> decimals])
         return   <logical>.
      setInt(<char> section, <char> entryname, value)
         return   <logical>.
      setNumber(<char> section, <char> entryname, value[, <int> decimals])
         return   <logical>.
   ----------------------------------------------------------------
   Notes:
   ----------------------------------------------------------------
      1) the getFloat and getNumber methods call the same code.
      2) the setFloat and setNumber methods call the same code.
      3) when working with number values:
         a) the set method will round based upon the
            current set("decimals") value.
         b) the getNumber, and setNumber methods will round
            based upon the current set("decimals") value or
            the decimals parameter when passed.
         c) the getInt and setInt methods always drop all
            decimals with no rounding.
         d) when debugging using the ? command, the display is
            always based upon the current set("decimals") value,
            even when there are more decimals present.
   ----------------------------------------------------------------
   */
#define  MAX_LINELENGTH       1024
#define  MAX_USERNAME         256
#define  ZEROS(n)             replicate(chr(0), n)
#define  RMMINI_PUBLISHER     "Rick Miller"
#define  RMMINI_VERSION       "8.0825"
//---------------------------------------------------------------//
//          constructor.
//---------------------------------------------------------------//
class rmmIni

   // protected methods.
   protect  x_GetUserName, x_InitializeExterns

   class::x_InitializeExterns()
   this.className =  "rmmIni"

   // assign this.file a default filename.
   this.file      =  set("directory") + "\" +;
                     class::x_GetUserName() + ".ini"

   this.publisher =  RMMINI_PUBLISHER
   this.version   =  RMMINI_VERSION
   //------------------------------------------------------------//
   //       end of constructor   -  methods below.
   //------------------------------------------------------------//
   // delete the file used with this object.
   function delete
      Local bRet, cFile, oFile
      cFile =  "" + this.file
      oFile =  new file()
      if oFile.exists(cFile)
         oFile.delete(cFile)
      endif
      bRet  =  iif(oFile.exists(cFile), false, true)
      oFile := null
   return   iif(bRet, true, false)
   //------------------------------------------------------------//
   // flush file writes that are in the cache.
   function flush
      Local bRet, cFile, oFile
      bRet  =  false
      cFile =  "" + this.file
      oFile =  new file()
      if oFile.exists(cFile)
         oFile.open(cFile, "W")
         if not oFile.handle == -1
            bRet  :=  RMM_FlushFileBuffers(int(oFile.handle))
         endif
         oFile.close()
      endif
      release object oFile
      oFile := null
   return   iif(bRet, true, false)

   //------------------------------------------------------------//
   // return a string value from this.file.
   function get(section, entry)
      Local cRet, nLen
      cRet  =  space(MAX_LINELENGTH)
      nLen  =  MAX_LINELENGTH
      // returns number of characters copied to the buffer,
      // not including the terminating null character.
      nLen  := RMM_GetIniString( ;
               "" + section, "" + entry, "", cRet, nLen, this.file)
   return   left(cRet, nLen)

   //------------------------------------------------------------//
   // return a float value from this.file.
   function getFloat(section, entry, nDecimals)
   return   this.getNumber(section, entry, nDecimals)

   //------------------------------------------------------------//
   // return an int value from this.file.
   function getInt(section, entry)
   return   int(val(this.get(section, entry)))

   //------------------------------------------------------------//
   // return a numeric value from this.file.
   function getNumber(section, entry, nDecimals)
      Local nRet, nDeci, nValu
      if ("" + nDecimals) == "false"
         nRet  =  val(this.get(section, entry))
      else
         nValu =  int(val("" + nDecimals))
         if nValu > 18
            nValu := 18
         elseif nValu < 0
            nValu := 0
         endif
         nDeci =  set("decimals")
         set decimals to (nValu)
         nRet  =  val("" + this.get(section, entry))
         set decimals to (nDeci)
      endif
   return   nRet

   //------------------------------------------------------------//
   // display object in the inspector.
   function inspect
      inspect(this)
   return

   //------------------------------------------------------------//
   // destroy this object. close procedure.
   function release
      try
         release object this
         close procedure (program(1))
      catch(exception e)
      endtry
   return

   //------------------------------------------------------------//
   // write a char into this.file.
   function set(section, entry, value)
   return   RMM_WriteIniString( ;
            "" + section, "" + entry, "" + value, "" + this.file)

   //------------------------------------------------------------//
   // write a float into this.file.
   function setFloat(section, entry, value, nDecimals)
   return   this.setNumber(section, entry, value, nDecimals)

   //------------------------------------------------------------//
   // write an int into this.file.
   function setInt(section, entry, value)
   return   this.set(section, entry,;
            ltrim(str(int(val("" + value)), 10, 0, "")))

   //------------------------------------------------------------//
   // write a number into this.file.
   function setNumber(section, entry, value, nDecimals)
      Local bRet, nDeci, nValu
      nDeci =  set("decimals")
      if ("" + nDecimals) == "false"
         bRet  =  this.set(section, entry,;
                  ltrim(str(val("" + value), 20, nDeci, "")))
      else
         nValu =  int(val("" + nDecimals))
         if nValu > 18
            nValu := 18
         elseif nValu < 0
            nValu := 0
         endif
         set decimals to (nValu)
         bRet  =  this.set(section, entry,;
                  ltrim(str(val("" + value), 20, nValu, "")))
         set decimals to (nDeci)
      endif
   return   iif(bRet, true, false)

   //------------------------------------------------------------//
   // get the user name logged into windows.
   function x_GetUserName
      Local cRet, sBuff, sLen
      sBuff =  ZEROS(MAX_USERNAME + 1)
      cRet  =  "Default"
      sLen  =  ZEROS(4)
      // put the length value into a pointer.
      sLen.setByte(0, bitand(MAX_USERNAME, 0xFF))
      sLen.setByte(1, bitand(bitzrshift(MAX_USERNAME,  8), 0xFF))
      sLen.setByte(2, bitand(bitzrshift(MAX_USERNAME, 16), 0xFF))
      sLen.setByte(3, bitand(bitzrshift(MAX_USERNAME, 24), 0xFF))
      if RMM_GetUserName(sBuff, sLen)
         // the variable pointed to by sLen contains the number
         // of characters copied to the buffer,
         // including the terminating null character.
         // ignore sLen and just find the first chr(0).
         cRet  := sBuff.left(sBuff.indexOf(chr(0)))
      endif
   return   cRet

   //------------------------------------------------------------//
   // declare external functions.
   function x_InitializeExterns
      if not type("RMM_FlushFileBuffers") == "FP"
         extern CLOGICAL RMM_FlushFileBuffers(CHANDLE) ;
            kernel32 from "FlushFileBuffers"
      endif
      if not type("RMM_GetIniString") == "FP"
         extern CULONG RMM_GetIniString( ;
            CSTRING, CSTRING, CSTRING, CSTRING, CULONG, CSTRING) ;
            kernel32 from "GetPrivateProfileStringA"
      endif
      if not type("RMM_GetUserName") == "FP"
         extern CLOGICAL RMM_GetUserName(CSTRING, CPTR) ;
            advapi32.dll from "GetUserNameA"
      endif
      if not type("RMM_WriteIniString") == "FP"
         extern CLOGICAL RMM_WriteIniString( ;
            CSTRING, CSTRING, CSTRING, CSTRING) ;
            kernel32 from "WritePrivateProfileStringA"
      endif
   return

endclass
//---------------------------------------------------------------//
//          end of rmmIni.
//---------------------------------------------------------------//