/****************************************************************************
    Kalkulon - A programmable calculator for programmers

    This file is part of the Kalkulon core.

    If you find this source code useful, your are encouraged to use it in your own projects.
    Please give me credit!

    COPYRIGHT by Juergen Holetzeck 2003 - 2008
    e-mail:		contact@kalkulon.de
    homepage:	www.kalkulon.de
****************************************************************************/


// DataObject.cpp: Implementierung der Klasse DataObject.
//
//////////////////////////////////////////////////////////////////////

#include "stdafx.h"

#include <sstream>

#include "DataObject.h"
#include "Token.h"
#include "commands.h"
#include "ErrBase.h"
#include "RPNStackEval.h"
#include "serialize.h"

using namespace Kalkulon;

// init statics
const std::string DataObject::STD_FORMAT("%.12g");
std::string DataObject::format_number(DataObject::STD_FORMAT);

//////////////////////////////////////////////////////////////////////////////
// special constructor for empty dob
DataObject::DataObject(DataObjectId e) : did(e), fnum_value(0), ptr_compound_value(nullptr)
{
    switch(did)
    {
    case STRING:
    case MESSAGE:
        ptr_compound_value = new std::string;
        break;

    case LIST:
        ptr_compound_value = new std::vector<DataObject>;
        break;

    case PUREFUNC:
        ptr_compound_value = new RPNStack_t;
        break;
    default:
        // do nothing special
        break;
    }
};


// copy constructor
DataObject::DataObject (const DataObject& rhs) : DataObject()
{
    did = rhs.did;
    if(FNUMBER==rhs.did)
    {
        fnum_value = rhs.fnum_value;
    }
    else
    {
        switch(rhs.did)
        {
            case STRING:
            case MESSAGE:
                ptr_compound_value = new std::string(*rhs.getPtrStrValue());
                break;
            case LIST:
                ptr_compound_value = new std::vector<DataObject>(*rhs.getPtrListValue());
                break;
            case PUREFUNC:
                ptr_compound_value = new RPNStack_t(*rhs.getPtrRPNStack());
                break;
            default:
                break;
        }
    }
};

DataObject& DataObject::operator=(const DataObject& rhs)
{
    if(this != &rhs)
    {
        // clean-up old content
        if(nullptr != ptr_compound_value)
            deleteCompound();

        did = rhs.did;
        if(FNUMBER==rhs.did)
        {
            fnum_value = rhs.fnum_value;
        }
        else
        {
            switch(rhs.did)
            {
                case STRING:
                case MESSAGE:
                    ptr_compound_value = new std::string(*rhs.getPtrStrValue());
                    break;
                case LIST:
                    ptr_compound_value = new std::vector<DataObject>(*rhs.getPtrListValue());
                    break;
                case PUREFUNC:
                    ptr_compound_value = new RPNStack_t(*rhs.getPtrRPNStack());
                    break;
                default:
                    break;
            }
        }
    }
    return *this;
};

// move constructor

DataObject::DataObject (DataObject&& rhs) NOEXCEPT
{
    did = rhs.did;
    fnum_value = rhs.fnum_value;
    ptr_compound_value = rhs.ptr_compound_value;

    //clean-up rhs
    rhs.did = UNKNOWN;
    rhs.ptr_compound_value = nullptr;
};

DataObject& DataObject::operator=(DataObject&& rhs) NOEXCEPT
{
    if(this != &rhs)
    {
        // clean-up old content
        if(nullptr != ptr_compound_value)
            deleteCompound();

        did = rhs.did;
        fnum_value = rhs.fnum_value;
        ptr_compound_value = rhs.ptr_compound_value;

        //clean-up rhs
        rhs.did = UNKNOWN;
        rhs.ptr_compound_value = nullptr;
    }
    return *this;
};

void DataObject::deleteCompound()
{
    switch (did)
    {
    case STRING:
    case MESSAGE:
        delete getPtrStrValue();
        break;

    case LIST:
        delete getPtrListValue();
        break;

    case PUREFUNC:
        delete getPtrRPNStack();
        break;
    default:
        break;
    }
    did = UNKNOWN;
    ptr_compound_value = nullptr;
}

//////////////////////////////////////////////////////////////////////////////
// overloaded operators and functions for DataObjects




DataObject DataObject::operator+(const DataObject& rhs) const
{
    if(this->did==STRING && rhs.did==STRING)
        {
            return DataObject(*(this->getPtrStrValue()) + *rhs.getPtrStrValue());
        }

    if(this->did==LIST && rhs.did==LIST)
        {
            DataObject dob(*this);
            dob.getPtrListValue()->insert(dob.getPtrListValue()->end(), rhs.getPtrListValue()->begin(), rhs.getPtrListValue()->end());
            return dob;
        }

    return DataObject(static_cast<double>(*this)+static_cast<double>(rhs));
}


bool DataObject::operator!=(const DataObject& rhs) const
{

    if((this->did==FNUMBER) && (rhs.did==FNUMBER))
  {
    return (this->fnum_value != rhs.fnum_value);
  }

    if(this->did==STRING && rhs.did==STRING)
  {
    return (*rhs.getPtrStrValue() != *(this->getPtrStrValue()));
  }

    if(this->did==LIST && rhs.did==LIST)
    {
            size_t s_rhs=rhs.getPtrListValue()->size();
            size_t s_lhs=getPtrListValue()->size();

            if(s_lhs!=s_rhs)
                return true;
            else
            {
                for(size_t i=0; i<s_lhs; ++i)
                    if((*getPtrListValue())[i] != (*rhs.getPtrListValue())[i])
                    {
                        return true;
                    }
            }
            return false;
    }

  if(this->did==PUREFUNC && rhs.did==PUREFUNC)
  {
    return (*rhs.getPtrRPNStack() != *(this->getPtrRPNStack()));
  }

    // type mismatch, return always unequal
    return true;
}


bool DataObject::operator==(const DataObject& rhs) const
{
    return !(rhs != *this);
}


DataObject& DataObject::operator[](size_t idx)
{
    switch(did)
    {
    case DataObject::LIST:
        if(idx<getPtrListValue()->size())
            return (*getPtrListValue())[idx];
        else
            throw ErrMathInternal("index","OUT OF RANGE");
    default:
        if(idx==0)
            return *this;
        else
            throw ErrMathInternal("index","OUT OF RANGE");
    }
}

const DataObject& DataObject::operator[](size_t idx) const
{
    switch(did)
    {
    case DataObject::LIST:
        if(idx<getPtrListValue()->size())
            return (*getPtrListValue())[idx];
        else
            throw ErrMathInternal("index","OUT OF RANGE");
    default:
        if(idx==0)
            return *this;
        else
            throw ErrMathInternal("index","OUT OF RANGE");
    }
}

//////////////////////////////////////////////////////////////////////////////
// conversions for DataObjects

DataObject::operator double() const
{
    switch(this->did)
    {
        case FNUMBER:
            return this->fnum_value;

        default:
            throw ErrMathInternal("double","TYPE MISMATCH");
    }
}


DataObject::operator std::string() const
{
    return formattedString(PLAIN);
}


// returns dob as formatted string
std::string DataObject::formattedString(unsigned int style) const
{
    switch(did)
    {
        case DataObject::FNUMBER:
            return sprintf(format_number, true /*single_number*/);
        case DataObject::STRING:
            if(style & QUOTEDSTRING)
                return '"'+*getPtrStrValue()+'"';
            else
                return *getPtrStrValue();
        case DataObject::LIST:
            {
                std::string out;
                size_t ls=getPtrListValue()->size();
                out += '{';
                for(size_t i=0; i<ls; ++i)
                {
                    out += (*getPtrListValue())[i].formattedString(style);
                    if(i<ls-1) out += ", ";
                }
                out += '}';
                return out;
            }
        case DataObject::MESSAGE:
            return *getPtrStrValue();
        case DataObject::PUREFUNC:
            {
                std::string out("<pure function");
                if(style & FULLFORM)
                {
                    RPNStack_t::iterator it		= getPtrRPNStack()->begin();
                    RPNStack_t::iterator itend	= getPtrRPNStack()->end();
                    out += ' ';
                    while(it != itend)
                    {
                        out += *(it++);
                        out += ' ';
                    }
                }
                out += '>';
                return out;
            }
        default:
            return "<empty>";
    }
}


// output stream operator
std::ostream& operator<< (std::ostream& out, DataObject& data )
{
    out << data.formattedString();
    return out;
}


std::string DataObject::sprintf(const std::string& format, bool single_number) const
{
    const int bufsize = 1023;
    char buf[bufsize+1]; buf[bufsize] = 0; // buf should always be null terminated
    std::string output;
    bool zeroFlag = false;	// handle "0" in "%0<width>b" manually, because "%0<width>s" seems to be implementation-defined

    // parse format string
    size_t s=format.size();
    size_t i=0;
    size_t idx=0;

    const DataObject& dob=*this;
    while(i<s)
    {
        char ch=format[i];
        std::string substr;

        // test for valid format string ('%%' -> '%')
        if('%'==ch && ++i<s && format[i]!='%')
        {
            substr = "%";
            // flags
            while( i<s &&
                    (
                    format[i]=='-' ||
                    format[i]=='+' ||
                    format[i]=='#' ||
                    format[i]==' ' ||
                    format[i]=='0'
                    )
                )
                {
                    if(format[i]=='0') zeroFlag=true;
                    substr += format[i++];
                }

            // width
            while(i<s && format[i]>='0' && format[i]<='9')
                substr += format[i++];

            // precision
            if(i<s && format[i]=='.')
            {
                substr += format[i++];
                while(i<s && format[i]>='0' && format[i]<='9')
                    substr += format[i++];
            }

            // type
            switch(format[i])
            {
                case 'i':
                case 'o':
                case 'u':
                case 'x':
                case 'X':
                {
                    substr += format[i];
                    double d = single_number?dob:dob[idx++];
                    if(d>=0)
                        SNPRINTF(buf, bufsize, substr.c_str(), static_cast<unsigned int>(d));
                    else
                        SNPRINTF(buf, bufsize, substr.c_str(), static_cast<int>(d));
                    output += buf;
                    break;
                }
                case 'e':
                case 'E':
                case 'f':
                case 'g':
                case 'G':
                    substr += format[i];
                    SNPRINTF(buf, bufsize, substr.c_str(), (double) (single_number?dob:dob[idx++]));
                    output += buf;
                    break;
                case 'b':
                    // non-standard option 'b' for binary output
                    {
                        substr += 's';
                        char numbuf[65];
                        double d = single_number?dob:dob[idx++];
                        #if _MSC_VER >= 1400
                            _itoa_s(static_cast<unsigned int>(d), numbuf, 65, 2);
                        #else
                            itoa(cast_u32(d), numbuf, 2);
                        #endif
                        SNPRINTF(buf, bufsize, substr.c_str(), numbuf);
                        if(zeroFlag) // fill output with prefixed '0'
                        {
                            size_t j=0;
                            while(buf[j]==' ') buf[j++]='0';
                        }
                        output += buf;
                    }
                    break;
                case 's':
                    if(!single_number)
                    {
                        substr += format[i];
                        SNPRINTF(buf, bufsize, substr.c_str(), (static_cast<std::string>(dob[idx++])).c_str());
                        output += buf;
                        break;
                    }
                    // else ...
                    // fall through because %s is not allowed for 'only_number'
                    // no break
                default:
                    // error
                    substr += format[i];
                    substr += "(format error)";
                    output += substr;
                    break;
            }
            zeroFlag = false;
            ++i;
        }
        else
        {
            output += ch;
            ++i;
        }
    }

    return output;
}


void DataObject::serialize(std::ostream& os) const
{
  os.write((char *) &did, sizeof(did));
  switch(did)
  {
    case DataObject::FNUMBER:
      serialize::put(os, fnum_value);
      break;
    case DataObject::STRING:
    {
      serialize::put(os, *getPtrStrValue());
      break;
    }
    case DataObject::LIST:
    {
      size_t len = getPtrListValue()->size();
      serialize::put(os, len);
      for(size_t i=0; i<len; ++i)
        ((*getPtrListValue())[i]).serialize(os);
      break;
    }
    case DataObject::PUREFUNC:
    {
      serialize::put(os, *getPtrRPNStack());
      break;
    }
    default:
      throw ErrInternal(__LINE__, __FILE__);
      break;
  }
}

DataObject DataObject::deserialize(std::istream& is)
{
  DataObjectId did;
  is.read((char *) &did, sizeof(did));
  switch(did)
  {
    case DataObject::FNUMBER:
      return DataObject(serialize::getDouble(is));
    case DataObject::STRING:
    {
      return DataObject(serialize::getString(is));
    }
    case DataObject::LIST:
    {
      size_t len = serialize::getSize_t(is);
      DataObject dob(LIST);
      dob.getPtrListValue()->resize(len);
      for(size_t i=0; i<len; ++i)
        (*dob.getPtrListValue())[i] = deserialize(is);
      return dob;
    }
    case DataObject::PUREFUNC:
    {
      DataObject dob(PUREFUNC);
      *dob.getPtrRPNStack() = serialize::getRPNStack_t(is);
      return dob;
    }
    default:
      throw ErrInternal(__LINE__, __FILE__);
      break;
  }
  return DataObject();
}

void serialize::put(std::ostream& os, const RPNStack_t& rpnStack)
{
  size_t len = rpnStack.size();
  serialize::put(os, len);

  RPNStack_t::const_iterator it		= rpnStack.begin();
  RPNStack_t::const_iterator itend	= rpnStack.end();
  while(it != itend)
  {
    (*(it++)).serialize(os);
  }

}

RPNStack_t serialize::getRPNStack_t(std::istream& is)
{
  RPNStack_t rpnStack;
  size_t len = serialize::getSize_t(is);
  for(size_t i=0; i<len; ++i)
  {
    Token tok = Token::deserialize(is);
    rpnStack.push_back(tok);
  }

  return rpnStack;
}



