//####COPYRIGHTBEGIN####
//                                                                          
// ----------------------------------------------------------------------------
// Copyright (C) 1998, 1999, 2000 Red Hat, Inc.
//
// This program is part of the eCos host tools.
//
// This program is free software; you can redistribute it and/or modify it 
// under the terms of the GNU General Public License as published by the Free 
// Software Foundation; either version 2 of the License, or (at your option) 
// any later version.
// 
// This program 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 General Public License for 
// more details.
// 
// You should have received a copy of the GNU General Public License along with
// this program; if not, write to the Free Software Foundation, Inc., 
// 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
//
// ----------------------------------------------------------------------------
//                                                                          
//####COPYRIGHTEND####
// Properties.cpp: implementation of the CProperties class.
//
//////////////////////////////////////////////////////////////////////
#include "Properties.h"
#include "eCosTrace.h"
//////////////////////////////////////////////////////////////////////
// Construction/Destruction
//////////////////////////////////////////////////////////////////////

CProperties::CProperties()
{
}

CProperties::~CProperties()
{
}

#ifdef _WIN32
bool CProperties::LoadFromRegistry(HKEY hTopKey,LPCTSTR szRegKey)
{
  HKEY hKey;
  LONG l=RegOpenKeyEx (hTopKey, szRegKey, 0L, KEY_QUERY_VALUE, &hKey);
  bool rc=(ERROR_SUCCESS==l);
  if(rc){
    TCHAR szName[256];
    DWORD dwSizeName=sizeof szName;
    DWORD dwMaxDatalen;
    DWORD dwType;
    if(ERROR_SUCCESS==RegQueryInfoKey(hKey,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,&dwMaxDatalen,NULL,NULL)){
      char *Data=new char[dwMaxDatalen];
      DWORD dwDatalen=dwMaxDatalen;
      for(DWORD dwIndex=0;ERROR_SUCCESS==RegEnumValue(hKey, dwIndex, szName, &dwSizeName, NULL, &dwType, (LPBYTE)Data, &dwDatalen);dwIndex++){ 
        
        CProperties::CProperty *p=Lookup(szName);
        if(p){
          switch(p->Type){
            case CProperty::Integer:
              if(REG_DWORD==dwType){
                p->SetValue(*(int *)Data);
              } else {
                TRACE(_T("Type mismatch - %s: expected REG_DWORD, got %d\n"),(LPCTSTR)p->strName,dwType);
                rc=false;
              }
              break;
            case CProperty::Bool:
              if(REG_DWORD==dwType){
                p->SetValue((bool)0!=*(int *)Data);
              } else {
                TRACE(_T("Type mismatch - %s: expected REG_DWORD, got %d\n"),(LPCTSTR)p->strName,dwType);
                rc=false;
              }
              break;
            case CProperty::Char:
              if(REG_DWORD==dwType){
                p->SetValue(*(char *)Data);
              } else {
                TRACE(_T("Type mismatch - %s: expected REG_DWORD, got %d\n"),(LPCTSTR)p->strName,dwType);
                rc=false;
              }
              break;
            case CProperty::Short:
              if(REG_DWORD==dwType){
                p->SetValue(*(short *)Data);
              } else {
                TRACE(_T("Type mismatch - %s: expected REG_DWORD, got %d\n"),(LPCTSTR)p->strName,dwType);
                rc=false;
              }
              break;
            case CProperty::Float:
            case CProperty::Double:
            case CProperty::szString: 
              if(REG_SZ==dwType){
                rc&=p->SetValue((LPCTSTR)Data);
              } else {
                TRACE(_T("Type mismatch - %s: expected REG_SZ, got %d\n"),(LPCTSTR)p->strName,dwType);
                rc=false;
              }
              break;
            case CProperty::Void:
              if(REG_BINARY==dwType){
                memcpy(p->pData,Data,min(dwDatalen,p->nLength));
              } else {
                TRACE(_T("Type mismatch - %s: expected REG_BINARY, got %d\n"),(LPCTSTR)p->strName,dwType);
                rc=false;
              }
              break;
          }
        } else {
          TRACE(_T("CProperties::LoadFromRegistry - unrecognized value %s in key %s\n"),szName,szRegKey);
          rc=false;
        }
        dwSizeName=sizeof szName;
        dwDatalen=dwMaxDatalen;
      }
      delete [] Data;
      dwSizeName=sizeof szName;
    }
    RegCloseKey(hKey);
  } else {
    TRACE(_T("Failed to open %s\n"),szRegKey);
  }
  
  return rc;
}

bool CProperties::SaveToRegistry(HKEY hTopKey,LPCTSTR szRegKey) const
{
  HKEY hKey;
  CreateKey(szRegKey);
  bool rc=(ERROR_SUCCESS==RegOpenKeyEx (hTopKey, szRegKey, 0L, KEY_SET_VALUE, &hKey));
  if(rc){
    for(int i=ar.size()-1;i>=0;--i){
      // Initializations are simply to avoid compiler warnings.
      DWORD dwDatalen=0; 
      DWORD dwType=REG_DWORD;
      BYTE *Data=0;
      // strValue and dw *must* be in scope for RegSetValueEx below.
      DWORD dw;
      String strValue;
      const CProperty &p=ar[i];
      switch(p.Type){
        case CProperties::CProperty::Integer:
        case CProperties::CProperty::Bool:
        case CProperties::CProperty::Char:
        case CProperties::CProperty::Short:
          dwType=REG_DWORD;
          dwDatalen=sizeof(DWORD);
          dw=p.GetValue();
          Data=(BYTE *)&dw;
          break;
        case CProperties::CProperty::Float:
        case CProperties::CProperty::Double:
        case CProperties::CProperty::szString:
          strValue=p.GetStringValue();
          Data=(BYTE *)(LPCTSTR)strValue;
          dwType=REG_SZ;
          dwDatalen=(1+strValue.size())*sizeof(_TCHAR);
          break;
        case CProperties::CProperty::Void:
          Data=(BYTE *)p.pData;
          dwType=REG_BINARY;
          dwDatalen=p.nLength;
          break;
        default:
          assert(false);
          break;
      }
      rc&=(ERROR_SUCCESS==RegSetValueEx(hKey,p.strName,0,dwType,Data,dwDatalen));
    }
  }   
  RegCloseKey(hKey);
  return rc;    
}

// Create all keys down to the one specified
bool CProperties::CreateKey(LPCTSTR pszKey,HKEY hKey/*=HKEY_CURRENT_USER*/)
{
  bool rc=true;
  LPCTSTR pcStart=pszKey;
  LPCTSTR pcEnd;
  do {
    HKEY hKey2;
    pcEnd=_tcschr(pcStart,_TCHAR('\\'));
    if(NULL==pcEnd){
      pcEnd=pcStart+_tcslen(pcStart);
    }
    String strKey(pcStart,pcEnd-pcStart);
    if(ERROR_SUCCESS!=RegCreateKeyEx(hKey,                // handle to an open key
      strKey,         // address of subkey name
      0,           // reserved
      0,           // address of class string
      REG_OPTION_NON_VOLATILE,          // special options flag
      KEY_ALL_ACCESS,        // desired security access
      NULL,
      // address of key security structure
      &hKey2,          // address of buffer for opened handle
      NULL// address of disposition value buffer);
      )){
      rc=false;
      break;
    }
    RegCloseKey(hKey);
    hKey=hKey2;
    pcStart=pcEnd+1;
  } while (_TCHAR('\0')!=*pcEnd);
  RegCloseKey(hKey);
  return rc;
}

#endif

bool CProperties::LoadFromCommandString(LPCTSTR psz)
{
  bool rc=true;
  const TCHAR *cNext;
  for(LPCTSTR c=_tcschr(psz,_TCHAR('-'));c;c=_tcschr(cNext,_TCHAR('-'))){
    c++;
    const TCHAR *pEq=_tcschr(c,_TCHAR('='));
    if(NULL==pEq){
      TRACE(_T("Failed to find '=' after %s\n"),c);
      rc=false;
      break;
    }
    String strName(c,pEq-c);
    CProperties::CProperty *p=Lookup(strName);
    c=pEq+1;
    String str;
    if(_TCHAR('"')==*c){
      // Value is a quoted string
      for(cNext=c+1;_TCHAR('"')!=*cNext;cNext++){
        if(_TCHAR('\\')==*cNext){
          cNext++;
        }
        str+=*cNext;
      }
    } else {
      // Value is simply terminated by whitespace
      for(cNext=c;_TCHAR('\0')!=*cNext && !_istspace(*cNext);cNext++);
      str=String(c,cNext-c);
    }
    if(p){
      rc&=p->SetValue(str);
    } else {
      TRACE(_T("Properties: unrecognized attribute %s in command string\n"),(LPCTSTR)strName);
      rc=false;
    }
    c=cNext;
  }
  return rc;
}

CProperties::CProperty * CProperties::Lookup(LPCTSTR pszName)
{
  for(int i=ar.size()-1;i>=0;--i){
    CProperties::CProperty &p=ar[i];
    if(0==_tcsicmp(p.strName,pszName)){
      return &p;
    }
  }
  return NULL;
}

String CProperties::MakeCommandString() const
{
  String strResult;
  bool bFirst=true;
  for(int i=ar.size()-1;i>=0;--i){
    String str;    
    const CProperty &p=ar[i];
    switch(p.Type){
      case CProperties::CProperty::Integer:
      case CProperties::CProperty::Bool:
      case CProperties::CProperty::Char:
      case CProperties::CProperty::Short:
        str.Format(_T("-%s=%u"),(LPCTSTR)p.strName,p.GetValue());
        break;
      case CProperties::CProperty::szString:
        {
          // Quote the string, escaping existing quotes as necessary
          str.Format(_T("-%s=\""),(LPCTSTR)p.strName);
          for(LPCTSTR c=p.GetStringValue();*c;c++){
            if(_TCHAR('"')==*c){
              str+=_TCHAR('\\');
            }
            str+=*c;
          }
          str+=_TCHAR('"');
        }
        break;
      case CProperties::CProperty::Float:
      case CProperties::CProperty::Double:
      case CProperties::CProperty::Void:
        str.Format(_T("-%s=%s"),(LPCTSTR)p.GetStringValue());
        break;
    }
    if(!bFirst){
      strResult+=_TCHAR(' ');
    }
    bFirst=false;
    strResult+=str;
  }
  return strResult;
}       

bool CProperties::CreatePathToFile(LPCTSTR pszDir) 
{
  // Create intermediate directories
#ifdef _WIN32
  const TCHAR cSep='\\';
#else // UNIX
  const TCHAR cSep='/';
#endif
  for(LPCTSTR c=_tcschr(pszDir,cSep);c;c=_tcschr(c+1,cSep)){
#ifdef _WIN32
    if(c==pszDir+2 && _istalpha(pszDir[0]) && _TCHAR(':')==pszDir[1]){
      continue; // don't attempt to create "C:"
    }
#endif
    String strDir(pszDir,c-pszDir);
    struct _stat buf;
    if(!(0==_tstat(strDir,&buf) && (S_IFDIR&buf.st_mode))){
      // Need to create directory
      bool b=(0==_tmkdir(strDir));
      TRACE(_T("Create directory %s rc=%d\n"),(LPCTSTR)strDir,b);
      if(!b){
        return false;
      }
    }
  }
  return true;
}

bool CProperties::SaveToFile(LPCTSTR pszFileName) const
{
  CreatePathToFile(pszFileName);
  FILE *f=_tfopen(pszFileName,_T("w") MODE_TEXT);
  if(f){
    for(int i=ar.size()-1;i>=0;--i){
      const CProperty &p=ar[i];
      String str(p.strName);
      str+=_TCHAR('=');
      switch(p.Type){
        case CProperties::CProperty::Integer:
        case CProperties::CProperty::Bool:
        case CProperties::CProperty::Char:
        case CProperties::CProperty::Short:
          str+=String::SFormat(_T("%u"),p.GetValue());
          break;
        case CProperties::CProperty::Float:
        case CProperties::CProperty::Double:
        case CProperties::CProperty::szString:
        case CProperties::CProperty::Void:
          str+=p.GetStringValue();
          break;
      }
      str+=_TCHAR('\n');
      _fputts(str,f);
    }
    fclose(f);
  }
  return (0!=f);
}

bool CProperties::LoadFromFile(LPCTSTR pszFileName)
{
  FILE *f=_tfopen(pszFileName,_T("r") MODE_TEXT);
  bool rc=(0!=f);
  if(rc){
    TCHAR buf[4096];
    int nLine=0;
    String str;
    while(_fgetts(buf,sizeof(buf)-1,f)){
      
      nLine++;
      int nLen=_tcslen(buf);
      if(nLen>0){
        // Remove trailing '\n'
        if(_TCHAR('\n')==buf[nLen-1]){
          buf[--nLen]=_TCHAR('\0');
        }
        // Remove trailing '\r'
        if(_TCHAR('\r')==buf[nLen-1]){
          buf[--nLen]=_TCHAR('\0');
        }
        
        // Check for continuation lines
        if(_TCHAR('\\')==buf[nLen-1]){
          buf[--nLen]=_TCHAR('\0');
          str+=buf;
        } else {
          str+=buf;
          LPCTSTR c=(LPCTSTR)str;
          const TCHAR *pEq=_tcschr(c,_TCHAR('='));
          if(pEq){
            const String strName(c,pEq-c);
            CProperties::CProperty *p=Lookup(strName);
            if(p){
              pEq++;
              rc&=p->SetValue(pEq);
            } else {
              ERROR(_T("Unknown attribute %s found in %s line %d\n"),(LPCTSTR)strName,pszFileName,nLine);
              rc=false;
            }
          }
          str=_T("");
        }
      }
    }
    fclose(f);
  }
  return rc;
}

CProperties::CProperty::CProperty(LPCTSTR pszName,Typetype type,void *_pData):
  strName(pszName),
  Type(type),
  pData(_pData)
{
}

CProperties::CProperty::~CProperty()
{
}

void CProperties::Add(LPCTSTR pszName,int &n) 
{
  CProperty p(pszName,CProperty::Integer,&n);
  ar.push_back(p);
}

void CProperties::Add(LPCTSTR pszName,unsigned int &n)
{
  CProperty p(pszName,CProperty::Integer,&n);
  ar.push_back(p);
}

void CProperties::Add(LPCTSTR pszName,bool &b)
{
  CProperty p(pszName,CProperty::Bool,&b);
  ar.push_back(p);
}

void CProperties::Add(LPCTSTR pszName,char &c)
{
  CProperty p(pszName,CProperty::Char,&c);
  ar.push_back(p);
}

void CProperties::Add(LPCTSTR pszName,unsigned char &c)
{
  CProperty p(pszName,CProperty::Char,&c);
  ar.push_back(p);
}

void CProperties::Add(LPCTSTR pszName,short &s)
{
  CProperty p(pszName,CProperty::Short,&s);
  ar.push_back(p);
}

void CProperties::Add(LPCTSTR pszName,unsigned short &s)
{
  CProperty p(pszName,CProperty::Short,&s);
  ar.push_back(p);
}

void CProperties::Add(LPCTSTR pszName,float &f)
{
  CProperty p(pszName,CProperty::Float,&f);
  ar.push_back(p);
}

void CProperties::Add(LPCTSTR pszName,double &f) 
{
  CProperty p(pszName,CProperty::Double,&f);
  ar.push_back(p);
}

void CProperties::Add(LPCTSTR pszName,void *pv,unsigned int _nLength)
{
  CProperty p(pszName,CProperty::Void,pv);
  p.nLength=_nLength;
  ar.push_back(p);
}

void CProperties::Add(LPCTSTR pszName,String &s)
{
  CProperty p(pszName,CProperty::szString,(void *)&s);
  ar.push_back(p);
}

unsigned long CProperties::CProperty::GetValue() const
{
  unsigned long dw;
  switch(Type){
    case Integer:
      dw=*(int *)pData;
      break;
    case Bool:
      dw=*(bool *)pData;
      break;
    case Char:
      dw=*(char *)pData;
      break;
    case Short:
      dw=*(short *)pData;
      break;
    default:
      dw=0;
      assert(false);
  }
  return dw;
}

const String CProperties::CProperty::GetStringValue() const 
{
  String str;
  switch(Type){
    case szString:
      str=*(String *)pData; 
      break;
    case CProperties::CProperty::Integer:
    case CProperties::CProperty::Bool:
    case CProperties::CProperty::Char:
    case CProperties::CProperty::Short:
      str.Format(_T("%u"),GetValue());
      break;
    case CProperties::CProperty::Float:
      str.Format(_T("%e"),*(float *)(pData));
      break;
    case CProperties::CProperty::Double:
      str.Format(_T("%e"),*(double *)(pData));
      break;
    case CProperties::CProperty::Void:
      {
        unsigned char *c=(unsigned char *)pData;
        for(unsigned int i=0;i<nLength;i++){
          TCHAR buf[3];
          _tprintf(buf,_T("%02x"),c[i]);
          str+=buf;
        }
      }
      break;
    default:
      break;
  }
  return str;
}

bool CProperties::CProperty::SetValue(int n)
{
  bool rc=true;
  switch(Type){
    case Integer:
      *(int *)(pData)=n;
      break;
    case Bool:
      *(bool *)(pData)=(0!=n);
      break;
    case Char:
      *(char *)(pData)=(char)n; //FIXME: range checks
      break;
    case Short:
      *(short *)(pData)=(short)n;//FIXME: range checks
      break;
    default:
      TRACE(_T("Failed to set '%s' to integer value '%d'\n"),(LPCTSTR)strName,n);
      break;
  }
  return rc;
}

bool CProperties::CProperty::SetValue(double n)
{
  bool rc=true;
  switch(Type){
    case Double:
      *(float *)(pData)=(float)n;//FIXME: range checks?
      break;
    case Float:
      *(double *)(pData)=n;
      break;
    default:
      TRACE(_T("Failed to set '%s' to double value '%f'\n"),(LPCTSTR)strName,n);
      rc=false;
      break;
  }
  
  return rc;
}

bool CProperties::CProperty::SetValue(LPCTSTR psz)
{
  bool rc=false;
  TCHAR *pEnd;
  double d=0.0;
  long l=0;
  switch(Type){
    case szString:
      *(String *)pData=psz;
      rc=true;
      break;
    case Float:
      d=_tcstod(psz,&pEnd);
      rc=(_TCHAR('\0')==*pEnd);
      if(rc){
        SetValue((float)d);
      }
      break;
    case Double:
      d=_tcstod(psz,&pEnd);
      rc=(_TCHAR('\0')==*pEnd);
      if(rc){
        SetValue(d);
      }
      break;
    case Integer:
    case Bool:
    case Char:
    case Short:
      l=_tcstol(psz,&pEnd,10);
      rc=(_TCHAR('\0')==*pEnd);
      if(rc){
        SetValue((int)l);
      }
      break;
    default:
      TRACE(_T("Failed to set '%s' to string value '%s'\n"),(LPCTSTR)strName,psz);
      break;
  }
  return rc;
}

