/***************************************************************************
                          SIMPLY POWERFUL TOOLKIT (SPTK)
                          CVariant.cpp  -  description
                             -------------------
    begin                : Tue Dec 14 1999
    copyright            : (C) 1999-2006 by Alexey Parshin
    email                : alexeyp@gmail.com
 ***************************************************************************/

/***************************************************************************
   This library is free software; you can redistribute it and/or modify it 
   under the terms of the GNU Library General Public License as published by
   the Free Software Foundation; either version 2 of the License, or (at 
   your option) any later version.

   This 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 Library
   General Public License for more details.

   You should have received a copy of the GNU Library General Public License
   along with this library; if not, write to the Free Software Foundation, 
   Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.

   Please report all bugs and problems to "alexeyp@gmail.com"
 ***************************************************************************/

#include <sptk3/CVariant.h>
#include <sptk3/CField.h>
#include <sptk3/CException.h>
#include <sptk3/string_ext.h>

#include <map>
#include <math.h>

using namespace std;
using namespace sptk;

void CVariant::releaseBuffers() {
   if (dataType() & (VAR_STRING|VAR_BUFFER|VAR_TEXT)) {
      if (m_data.buffer.data) {
         free(m_data.buffer.data);
         m_data.buffer.data = NULL;
         m_data.buffer.size = 0;
      }
   }
}
//---------------------------------------------------------------------------
void CVariant::setInteger(long value) {
   releaseBuffers();
   dataType(VAR_INT);
   dataSize(sizeof(value));
   m_data.intData = value;
   m_null = false;
}
//---------------------------------------------------------------------------
void CVariant::setFloat(double value) {
   releaseBuffers();
   dataType(VAR_FLOAT);
   dataSize(sizeof(value));
   m_data.floatData = value;
   m_null = false;
}
//---------------------------------------------------------------------------
void CVariant::setMoney(double value) {
   releaseBuffers();
   dataType(VAR_MONEY);
   dataSize(sizeof(value));
   m_data.floatData = value;
   m_null = false;
}
//---------------------------------------------------------------------------
void CVariant::setString(const char * value,int maxlen) {
   m_null = false;
   if (dataType() == VAR_STRING && maxlen && (int)m_data.buffer.size == maxlen+1) {
      if (value) {
         strncpy(m_data.buffer.data,value,maxlen);
         m_data.buffer.data[maxlen] = 0;
      } else {
         m_data.buffer.data[0] = 0;
         m_null = true;
      }
   } else {
      releaseBuffers();
      if (value) {
         if (maxlen) {
            dataSize(maxlen);
            m_data.buffer.size = maxlen + 1;
            m_data.buffer.data = (char *)malloc(m_data.buffer.size);
            strncpy(m_data.buffer.data,value,maxlen);
            m_data.buffer.data[maxlen] = 0;
         } else {
            dataSize(strlen(value));
            m_data.buffer.size = dataSize() + 1;
            m_data.buffer.data = strdup(value);
            m_null = false;
         }
      } else {
         m_data.buffer.data = NULL;
         dataSize(0);
         m_data.buffer.size = 0;
         m_null = true;
      }
   }
   dataType(VAR_STRING);
}
//---------------------------------------------------------------------------
void CVariant::setText(const char * value) {
   releaseBuffers();
   dataType(VAR_TEXT);
   if (value) {
      m_null = false;
      dataSize(strlen(value));
      m_data.buffer.size = dataSize() + 1;
      m_data.buffer.data = strdup(value);
   } else {
      m_null = true;
      m_data.buffer.data = NULL;
      m_data.buffer.size = 0;
      dataSize(0);
   }
}
//---------------------------------------------------------------------------
void CVariant::setText(const string& value) {
   releaseBuffers();
   dataType(VAR_TEXT);
   unsigned vlen = value.length();
   if (vlen) {
      m_null = false;
      dataSize(vlen);
      m_data.buffer.size = vlen + 1;
      m_data.buffer.data = strdup(value.c_str());
   } else {
      m_null = true;
      m_data.buffer.data = NULL;
      m_data.buffer.size = 0;
      dataSize(0);
   }
}
//---------------------------------------------------------------------------
void CVariant::setBuffer(const void * value,int sz) {
   releaseBuffers();
   dataType(VAR_BUFFER);
   if (value) {
      m_null = false;
      m_data.buffer.size = sz;
      dataSize(sz);
      m_data.buffer.data = (char *)malloc(sz);
      memcpy(m_data.buffer.data,value,sz);
   } else 
      setNull();
}
//---------------------------------------------------------------------------
void CVariant::setBuffer(const string& value) {
   releaseBuffers();
   dataType(VAR_BUFFER);
   unsigned vlen = value.length();
   if (vlen) {
      int sz = vlen + 1;
      m_null = false;
      m_data.buffer.size = sz;
      dataSize(sz);
      m_data.buffer.data = (char *)malloc(sz);
      memcpy(m_data.buffer.data,value.c_str(),sz);
   } else 
      setNull();
}
//---------------------------------------------------------------------------
void CVariant::setDateTime(CDateTime value) {
   m_null = false;
   releaseBuffers();
   dataType(VAR_DATE_TIME);
   dataSize(sizeof(value));
   m_data.floatData = value;
}
//---------------------------------------------------------------------------
void CVariant::setDate(CDateTime value) {
   m_null = false;
   releaseBuffers();
   dataType(VAR_DATE);
   dataSize(sizeof(value));
   m_data.floatData = (int)value;
}
//---------------------------------------------------------------------------
void CVariant::setImagePtr(const void *value) {
   m_null = false;
   releaseBuffers();
   dataType(VAR_IMAGE_PTR);
   m_data.buffer.size = 0;
   dataSize(sizeof(value));
   m_data.imagePtr = (void *) value;
}
//---------------------------------------------------------------------------
void CVariant::setData(const CVariant &C) {
   m_null = C.m_null;
   switch (C.dataType()) {
      case VAR_INT:       setInteger(C.getInteger());            break;
      case VAR_FLOAT:     setFloat(C.getFloat());                break;
      case VAR_MONEY:     setFloat(C.getFloat());                break;
      case VAR_STRING:    setString(C.getString());              break;
      case VAR_TEXT:      setText(C.getText());                  break;
      case VAR_BUFFER:    setBuffer(C.getBuffer(),C.dataSize()); break;
      case VAR_DATE:      setDate(C.getDateTime());              break;
      case VAR_DATE_TIME: setDateTime(C.getDateTime());          break;
      case VAR_IMAGE_PTR: setImagePtr(C.getImagePtr());          break;
      case VAR_NONE:      break;
   }
}
//---------------------------------------------------------------------------
long CVariant::getInteger() const {
   return m_data.intData;
}
//---------------------------------------------------------------------------
double CVariant::getFloat() const {
   return m_data.floatData;
}
//---------------------------------------------------------------------------
const char * CVariant::getString() const {
   return m_data.buffer.data;
}
//---------------------------------------------------------------------------
const void * CVariant::getBuffer() const {
   return m_data.buffer.data;
}
//---------------------------------------------------------------------------
const char * CVariant::getText() const {
   return m_data.buffer.data;
}
//---------------------------------------------------------------------------
CDateTime CVariant::getDateTime() const {
   return m_data.floatData;
}
//---------------------------------------------------------------------------
CDateTime CVariant::getDate() const {
   return (int)m_data.floatData;
}
//---------------------------------------------------------------------------
void *CVariant::getImagePtr() const {
   return m_data.imagePtr;
}
//---------------------------------------------------------------------------
// convertors
long CVariant::asInteger() const throw(CException) {
   if (m_null) return 0;
   switch (m_dataType) {
      case VAR_INT:        return m_data.intData;
      case VAR_MONEY:
      case VAR_FLOAT:      return (long)m_data.floatData;
      case VAR_STRING:
      case VAR_TEXT:
      case VAR_BUFFER:     return atol(m_data.buffer.data);
      case VAR_DATE:
      case VAR_DATE_TIME:  return int(m_data.floatData);
      default:             throw CException("Can't convert field for that type");
   }
}

bool CVariant::asBool() const throw(CException) {
   char ch;
   if (m_null) return true;
   switch (dataType()) {
      case VAR_INT:        return (m_data.intData>0);
      case VAR_MONEY:
      case VAR_FLOAT:      return (m_data.floatData>0);
      case VAR_STRING:
      case VAR_TEXT:
      case VAR_BUFFER:     
         ch = m_data.buffer.data[0];
         return (strchr("YyTt1",ch)!=0);
      case VAR_DATE:       
      case VAR_DATE_TIME:  return bool(m_data.floatData!=0);
      default:             throw CException("Can't convert field for that type");
   }
}

double CVariant::asFloat() const throw(CException) {
   if (m_null) return 0;
   switch (dataType()) {
      case VAR_INT:        return m_data.intData;
      case VAR_MONEY:
      case VAR_FLOAT:      return m_data.floatData;
      case VAR_STRING:
      case VAR_TEXT:
      case VAR_BUFFER:     return strtod(m_data.buffer.data, 0);
      case VAR_DATE:       
      case VAR_DATE_TIME:  return m_data.floatData;
      default:             throw CException("Can't convert field for that type");
   }
}

string CVariant::asString() const throw(CException) {
   if (m_null) return "";
   char print_buffer[64];
   switch (dataType()) {
      case VAR_NONE:
         return "";
      case VAR_INT:
         sprintf(print_buffer,"%li",m_data.intData);
         return string(print_buffer);
      case VAR_MONEY:
      case VAR_FLOAT:
         {
            const char *formatString = "%0.4f";
            if (floor(m_data.floatData) == m_data.floatData)
               formatString = "%0.0f";
            if (fabs(m_data.floatData) > 1e16)
               formatString = "%0.4e";
            sprintf(print_buffer,formatString,m_data.floatData);                
            return string(print_buffer);
         }
      case VAR_STRING:
      case VAR_TEXT:
      case VAR_BUFFER:     return m_data.buffer.data;
      case VAR_DATE:       return CDateTime(m_data.floatData).dateString();
      case VAR_DATE_TIME:   {
            CDateTime dt(m_data.floatData);
            return dt.dateString() + " " + dt.timeString();
         }
      case VAR_IMAGE_PTR:
         sprintf(print_buffer,"%p",(char *)m_data.imagePtr);
         return string(print_buffer);
      default:             throw CException("Can't convert field for that type");
   }
}

CDateTime CVariant::asDate() const throw(CException) {
   if (m_null) return 0.0;
   switch (dataType()) {
      case VAR_INT:        return m_data.intData;
      case VAR_MONEY:
      case VAR_FLOAT:      return m_data.floatData;

      case VAR_STRING:
      case VAR_TEXT:
      case VAR_BUFFER:     return m_data.buffer.data;

      case VAR_DATE:
      case VAR_DATE_TIME:   return int(m_data.floatData);

      default:             throw CException("Can't convert field for that type");
   }
}

CDateTime CVariant::asDateTime() const throw(CException) {
   if (m_null) return 0.0;
   switch (dataType()) {
      case VAR_INT:        return m_data.intData;
      case VAR_MONEY:
      case VAR_FLOAT:      return m_data.floatData;

      case VAR_STRING:
      case VAR_TEXT:
      case VAR_BUFFER:     return m_data.buffer.data;

      case VAR_DATE:
      case VAR_DATE_TIME:  return m_data.floatData;

      default:             throw CException("Can't convert field for that type");
   }
}

void *CVariant::asImagePtr() const throw(CException) {
   if (m_null) return 0;
   switch (dataType()) {
      case VAR_INT:        return (void *)m_data.intData;
      case VAR_IMAGE_PTR:  return m_data.imagePtr;
      default:             throw CException("Can't convert field for that type");
   }
}

void CVariant::setNull() {
   switch (dataType()) {
      default:        		
         m_data.intData = 0;
         break;

      case VAR_MONEY:
      case VAR_FLOAT:      
         m_data.floatData = 0;
         break;

      case VAR_STRING:
      case VAR_TEXT:
      case VAR_BUFFER:     
         if (m_data.buffer.data) m_data.buffer.data[0] = 0;
         break;

      case VAR_DATE:
      case VAR_DATE_TIME:  
         m_data.floatData = 0;
         break;
   }
   m_dataSize = 0;
   m_null = true; 
}

string CVariant::typeName(CVariantType type) {
   switch (type) {
   default:            return "undefined";
   case VAR_INT:       return "int";
   case VAR_FLOAT:     return "double";
   case VAR_MONEY:     return "money";
   case VAR_STRING:    return "string";
   case VAR_TEXT:      return "text";
   case VAR_BUFFER:    return "blob";
   case VAR_DATE:      return "date";
   case VAR_DATE_TIME: return "datetime";
   case VAR_IMAGE_PTR: return "imageptr";
   }
} 

CVariantType CVariant::nameType(const char* name) {
   static std::map<string,CVariantType> nameToTypeMap;
   if (nameToTypeMap.empty()) {
      for (int type = VAR_INT; type <= VAR_IMAGE_PTR; type*=2) {
         CVariantType vtype = (CVariantType) type;
         nameToTypeMap[typeName(vtype)] = vtype;
      }
   }
   std::map<string,CVariantType>::iterator itor = nameToTypeMap.find(lowerCase(name));
   if (itor == nameToTypeMap.end())
      throw CException("Type name "+string(name)+" isn't recognized",__FILE__,__LINE__);
   return itor->second;
}

void CVariant::load(const CXmlNode& node) {
   const string& ntype = node.getAttribute("type");
   unsigned type = nameType(ntype.c_str());
   switch (type) {
   case VAR_INT:
   case VAR_FLOAT:
   case VAR_MONEY:
   case VAR_STRING:
   case VAR_DATE:
   case VAR_DATE_TIME:
        *this = node.text();
        break;
   case VAR_TEXT:
   case VAR_BUFFER:
        *this = node.text();
        break;
   default:
   case VAR_IMAGE_PTR:
        break;
   }
}

void CVariant::save(CXmlNode& node) const {
   string stringValue(asString());
   node.clear();
   node.name("data");
   node.attributes["type"] = typeName(dataType());

   if (!stringValue.empty()) {
       switch (dataType()) {
       case VAR_INT:
       case VAR_FLOAT:
       case VAR_MONEY:
       case VAR_STRING:
       case VAR_DATE:
       case VAR_DATE_TIME: 
           new CXmlText(node,stringValue);
           break;
       case VAR_TEXT:
       case VAR_BUFFER:
           new CXmlCDataSection(node,asString());
           break;
       default:
       case VAR_IMAGE_PTR:
           break;
       }
   }
}
