#include <thread>
#include <iterator>

#include "util/Util.h"
#include "util/simpleobject/AgString.h"
#include "expression/Expression.h"
#include "util/simpleobject/AgDate.h"
#include "util/simpleobject/AgBoolean.h"
#include "datatable/TableFormat.h"
#include "Cres.h"
#include "IllegalArgumentException.h"
#include "datatable/DataTable.h"

bool Util::equals(std::vector< std::pair<AgObjectPtr,AgString> > &l, std::vector< std::pair<AgObjectPtr,AgString> > &r)
{
    if (l.size() != r.size())
        return false;

    for (unsigned int i = 0; i < l.size(); ++i)
    {
        if (l[i].first && (!l[i].first->equals(r[i].first.get())))
            return false;

        if (l[i].second != r[i].second)
            return false;
    }

    return true;
}

bool Util::equals(ExpressionPtr &l, ExpressionPtr &r)
{
    if (l.get() == NULL && r.get() == NULL)
    {
        return true;
    }
    else if (l.get() == NULL && r.get() != NULL)
    {
        return false;
    }
    else if (l.get() != NULL && r.get() == NULL)
    {
        return false;
    }
    else
    {
        return l->equals(&(*r));
    }
}

bool Util::equals(TableFormatPtr &l, TableFormatPtr &r)
{
    return l->equals(r.get());
}

bool Util::equals(DataRecordPtr &/*l*/, DataRecordPtr &/*r*/)
{
    assert(0);
    return false;
}


std::vector<char> Util::readStream(std::iostream& is)
{
    typedef std::istream_iterator<char> istream_iterator;

    std::vector<char> buffer;
    std::copy(istream_iterator(is), istream_iterator(), std::back_inserter(buffer));

    return buffer;
}


AgObject* Util::convertToNumber(AgObjectPtr value, bool validate, bool allowNull)
{
    if (value.get() == NULL)
    {
        if (allowNull)
        {
            return NULL;
        }
        if (validate)
        {
            throw IllegalArgumentException(Cres::get()->getString("utCannotConvertToNumber") + getObjectDescription(value));
        }
        return new AgInteger(0);
    }

    DataTable *dt = dynamic_cast<DataTable *>(value.get());
    if (dt)
    {
        return convertToNumber(dt->get(), validate, allowNull);
    }
    AgInteger *ai = dynamic_cast<AgInteger *>(value.get());
    if (ai)
    {
        return new AgInteger(ai->getValue());
    }
    AgLong *al = dynamic_cast<AgLong *>(value.get());
    if (al)
    {
        return new AgLong(al->getValue());
    }
    AgDouble *ad = dynamic_cast<AgDouble *>(value.get());
    if (ad)
    {
        return new AgDouble(ad->getValue());
    }
    AgFloat *af = dynamic_cast<AgFloat *>(value.get());
    if (af)
    {
        return new AgFloat(af->getValue());
    }

    AgDate *adate = dynamic_cast<AgDate *>(value.get());
    if (adate)
    {
        return new AgLong(adate->toTime());
    }
    AgBoolean *abool = dynamic_cast<AgBoolean *>(value.get());
    if (abool)
    {
        return new AgInteger(abool->getValue() ? 1 : 0);
    }

     try
     {
        AgString str = value->toString();
        return new AgLong(str.toInt64());
     }
     catch (...)
     {
     }

     try
     {
        AgString str = value->toString();
        return new AgFloat(str.toFloat());
     }
     catch (...)
     {
     }

     if (validate)
     {
        throw IllegalArgumentException(Cres::get()->getString("utCannotConvertToNumber") + getObjectDescription(value));
     }
     else
     {
        return allowNull ? NULL : new AgInteger(0);
     }
}


bool Util::convertToBoolean(AgObjectPtr value, bool validate, bool allowNull)
{
    if (value.get() == NULL)
    {
        if (allowNull)
        {
            assert(0);
            return false;
        }
        if (validate)
        {
            throw IllegalArgumentException(Cres::get()->getString("utCannotConvertToBoolean") + getObjectDescription(value));
        }
        return false;
    }

    DataTable *dt = dynamic_cast<DataTable *>(value.get());
    if (dt)
    {
        return convertToBoolean(dt->get(), validate, allowNull);
    }
    AgBoolean *abool = dynamic_cast<AgBoolean *>(value.get());
    if (abool)
    {
        return abool->getValue();
    }

    assert(0);
    return false;
}


AgString Util::getObjectDescription(AgObjectPtr o)
{
    if (o == NULL)
    {
        return "null";
    }

    return o->toString() + " (" + AgString(o->getClass().c_str()) + ")";
}


int Util::parseVersion(const AgString& version)
{
    int major = AgString(version.substr(0, 1)).toInt();
    int minor = AgString(version.substr(2, 4)).toInt();
    int build = AgString(version.substr(5, 7)).toInt();

    return major * 10000 + minor * 100 + build;
}

AgString Util::nameToDescription(const AgString& name)
{
    AgString ss;

    bool prevWasUpper = false;
    bool nextToUpper = false;

    for (unsigned int i = 0; i < name.length(); i++)
    {
        char c = (char)name.at(i);

        if (isupper(c))
        {
            if (!prevWasUpper && i != 0)
            {
              ss += AgString(" ");
            }
            prevWasUpper = true;
        }else {
            prevWasUpper = false;
        }

        if (i == 0 || nextToUpper) {
            c = toupper(c);
            nextToUpper = false;
        }

        if (c == '_') {
            ss += AgString(" ");
            nextToUpper = true;
        }else{
            ss += c;
        }
    }

    return ss;
}

AgString Util::descriptionToName(const AgString& /*value*/)
{
    assert(0);
    AgString ss;
    return ss;
}


bool Util::equals(AgObjectPtr &/*l*/, AgObjectPtr &/*r*/)
{
    assert(0);
    return false;
}

unsigned int Util::getAvailableProcessors()
{
    unsigned int concurentThreadsSupported = std::thread::hardware_concurrency();
    if (concurentThreadsSupported == 0) concurentThreadsSupported = 4;
    return concurentThreadsSupported;
}

milliseconds Util::getCurrentMilliseconds()
{
    return duration_cast< milliseconds >(system_clock::now().time_since_epoch());
}
