/***************************** LICENSE START ***********************************

 Copyright 2012 ECMWF and INPE. This software is distributed under the terms
 of the Apache License version 2.0. In applying this license, ECMWF does not
 waive the privileges and immunities granted to it by virtue of its status as
 an Intergovernmental Organization or submit itself to any jurisdiction.

 ***************************** LICENSE END *************************************/

#include "MvKeyProfile.h"

#include <cassert>

#include <iostream>
#include <sstream>
#include <cstdio>

#include "MvVariant.h"
#include "MvMiscellaneous.h"

//-----------------------------------
// MvKey
//-----------------------------------

MvKey::MvKey() :
    name_(""),
    shortName_(""),
    description_(""),
    role_(NoRole),
    keyType_(NormalKey),
    precision_(0),
    valueType_(StringType),
    constant_(false),
    intMissingVal_(0),
    longMissingVal_(0),
    floatMissingVal_(0.),
    doubleMissingVal_(0.),
    missingValDefined_(false),
    readIntAsString_(true),
    editable_(true)
{
}

MvKey::MvKey(const std::string& n, const std::string& sn, std::string d) :
    name_(n),
    shortName_(sn),
    description_(d),
    role_(NoRole),
    keyType_(NormalKey),
    precision_(0),
    valueType_(StringType),
    constant_(false),
    intMissingVal_(0),
    longMissingVal_(0),
    floatMissingVal_(0.),
    doubleMissingVal_(0.),
    missingValDefined_(false),
    readIntAsString_(true),
    editable_(true)
{
}

MvKey::MvKey(const MvKey& o)
{
    name_              = o.name();
    shortName_         = o.shortName();
    description_       = o.description();
    role_              = o.role_;
    keyType_           = o.keyType_,
    precision_         = o.precision_;
    valueType_         = o.valueType_;
    constant_          = o.constant_;
    metaData_          = o.metaData();
    intMissingVal_     = o.intMissingVal_;
    longMissingVal_    = o.longMissingVal_;
    floatMissingVal_   = o.floatMissingVal_;
    doubleMissingVal_  = o.doubleMissingVal_;
    missingValDefined_ = o.missingValDefined_;
    readIntAsString_   = o.readIntAsString();
    editable_          = o.editable();
}

MvKey* MvKey::clone()
{
    auto* cp = new MvKey(*this);
    return cp;
}

void MvKey::setValueType(ValueType t, bool useMissingValue)
{
    if (valueType_ != t) {
        int n = valueNum();
        clearData();

        valueType_ = t;
        if (t == LongType) {
            long val = (useMissingValue) ? longMissingVal_ : 0;
            longVal_ = std::vector<long>(n, val);
        }
        else if (t == IntType) {
            int val = (useMissingValue) ? intMissingVal_ : 0;
            intVal_ = std::vector<int>(n, val);
        }
        else if (t == DoubleType) {
            double val = (useMissingValue) ? doubleMissingVal_ : 0.;
            doubleVal_ = std::vector<double>(n, val);
        }
        else if (t == FloatType) {
            float val = (useMissingValue) ? floatMissingVal_ : 0.;
            floatVal_ = std::vector<float>(n, val);
        }
        else if (t == StringType) {
            stringVal_ = std::vector<std::string>(n);
        }
    }
}

void MvKey::addStringValue(const std::string& v)
{
    assert(valueType_ == StringType);
    stringVal_.push_back(v);
}

void MvKey::setStringValue(int idx, const std::string& v)
{
    assert(valueType_ == StringType);
    if (idx >= 0 && idx < static_cast<int>(stringVal_.size())) {
        stringVal_[idx] = v;
    }
}

std::string MvKey::valueAsString(int idx) const
{
    if (valueType_ == StringType) {
        assert(idx >= 0 && idx < static_cast<int>(stringVal_.size()));
        if (constant_)
            return stringConstantValue();
        else
            return stringVal_[idx];
    }
    else {
        if (valueType_ == LongType) {
            assert(idx >= 0 && idx < static_cast<int>(longVal_.size()));
            return std::to_string((constant_ ? longConstantValue() : longVal_[idx]));
        }
        else if (valueType_ == IntType) {
            assert(idx >= 0 && idx < static_cast<int>(intVal_.size()));
            return std::to_string((constant_ ? intConstantValue() : intVal_[idx]));
        }
        else if (valueType_ == DoubleType) {
            assert(idx >= 0 && idx < static_cast<int>(doubleVal_.size()));
            return std::to_string((constant_ ? doubleConstantValue() : doubleVal_[idx]));
        }
        /*else if(valueType_ == FloatType)
        {
            if(idx >= 0 && idx < static_cast<int>(floatVal_.size()));
            return std::to_string((constant_?floatConstantValue():floatVal_[idx]));
        }*/
    }


    static std::string es;
    return es;
}

const std::string& MvKey::stringConstantValue() const
{
    if (constant_) {
        if (stringVal_.size() > 0)
            return stringVal_[0];
    }
    static std::string es;
    return es;
}

void MvKey::addIntValue(int v)
{
    assert(valueType_ == IntType);
    intVal_.push_back(v);
}

void MvKey::setIntValue(int idx, int v)
{
    assert(valueType_ == IntType);
    if (idx >= 0 && idx < static_cast<int>(intVal_.size())) {
        intVal_[idx] = v;
    }
}

int MvKey::intConstantValue() const
{
    if (constant_) {
        if (intVal_.size() > 0)
            return intVal_[0];
    }
    return -9999999;
}

void MvKey::setIntRange(int v1, int v2)
{
    if (constant_)
        constant_ = false;

    intVal_.clear();
    for (int i = v1; i <= v2; i++)
        intVal_.push_back(i);
}

void MvKey::addLongValue(long v)
{
    assert(valueType_ == LongType);
    longVal_.push_back(v);
}

void MvKey::setLongValue(int idx, long v)
{
    assert(valueType_ == LongType);
    if (idx >= 0 && idx < static_cast<int>(longVal_.size())) {
        longVal_[idx] = v;
    }
}

long MvKey::longConstantValue() const
{
    if (constant_) {
        if (longVal_.size() > 0)
            return longVal_[0];
    }
    return -9999999;
}

void MvKey::setLongRange(long v1, long v2)
{
    if (constant_)
        constant_ = false;

    longVal_.clear();
    for (long i = v1; i <= v2; i++)
        longVal_.push_back(i);
}

void MvKey::addDoubleValue(double v)
{
    doubleVal_.push_back(v);
}

void MvKey::setDoubleValue(int idx, double v)
{
    if (idx >= 0 && idx < static_cast<int>(doubleVal_.size())) {
        doubleVal_[idx] = v;
    }
}

double MvKey::doubleConstantValue() const
{
    if (constant_) {
        if (doubleVal_.size() > 0)
            return doubleVal_[0];
    }
    return -9999999.;
}

void MvKey::addValue(const MvVariant& var)
{
    if (valueType_ == IntType) {
        addIntValue(var.toInt());
    }
    if (valueType_ == LongType) {
        addLongValue(var.toLong());
    }
    else if (valueType_ == FloatType) {
        addDoubleValue(var.toDouble());
    }
    else if (valueType_ == DoubleType) {
        addDoubleValue(var.toDouble());
    }
    else if (valueType_ == StringType) {
        addStringValue(var.toString());
    }
    else {
        addDoubleValue(var.toDouble());
    }
}

void MvKey::setValue(int chunkStart, int chunkSize, MvKey* key)
{
    int chunkEnd = chunkStart + chunkSize;
    assert(!constant_);
    if (constant_)
        return;

    if (valueType_ == IntType) {
        for (int i = chunkStart; i < chunkEnd; i++)
            intVal_[i] = key->intVal_[i - chunkStart];
    }
    if (valueType_ == LongType) {
        for (int i = chunkStart; i < chunkEnd; i++)
            longVal_[i] = key->longVal_[i - chunkStart];
    }
    else if (valueType_ == FloatType) {
        for (int i = chunkStart; i < chunkEnd; i++)
            floatVal_[i] = key->floatVal_[i - chunkStart];
    }
    else if (valueType_ == DoubleType) {
        for (int i = chunkStart; i < chunkEnd; i++)
            doubleVal_[i] = key->doubleVal_[i - chunkStart];
    }
    else if (valueType_ == StringType) {
        for (int i = chunkStart; i < chunkEnd; i++)
            stringVal_[i] = key->stringVal_[i - chunkStart];
    }
}

void MvKey::clearData()
{
    stringVal_.clear();
    intVal_.clear();
    longVal_.clear();
    floatVal_.clear();
    doubleVal_.clear();
}

const std::string& MvKey::metaData(const std::string& key) const
{
    static std::string es;
    auto it = metaData_.find(key);
    return (it != metaData_.end()) ? it->second : es;
}

void MvKey::setMetaData(const std::string& key, const std::string& value)
{
    metaData_[key] = value;
}


void MvKey::clearMetaData()
{
    metaData_.clear();
}

int MvKey::valueNum() const
{
    if (valueType_ == StringType) {
        return static_cast<int>(stringVal_.size());
    }
    else if (valueType_ == IntType) {
        return static_cast<int>(intVal_.size());
    }
    else if (valueType_ == LongType) {
        return static_cast<int>(longVal_.size());
    }
    else if (valueType_ == FloatType) {
        return static_cast<int>(floatVal_.size());
    }
    else if (valueType_ == DoubleType) {
        return static_cast<int>(doubleVal_.size());
    }

    return 0;
}

void MvKey::preAllocate(int num)
{
    assert(num > 0);

    if (valueType_ == StringType) {
        stringVal_ = std::vector<std::string>(num);
    }
    else if (valueType_ == IntType) {
        intVal_ = std::vector<int>(num, 0);
    }
    else if (valueType_ == LongType) {
        longVal_ = std::vector<long>(num, 0);
    }
    else if (valueType_ == FloatType) {
        floatVal_ = std::vector<float>(num, 0.);
    }
    else if (valueType_ == DoubleType) {
        doubleVal_ = std::vector<double>(num, 0.);
    }
}

bool MvKey::operator==(const MvKey& o) const
{
    return name_ == o.name_ &&
           shortName_ == o.shortName_ &&
           description_ == o.description_;
}

bool MvKey::operator!=(const MvKey& o) const
{
    return !(o == *this);
}


//-----------------------------------
// MvKeyProfile
//-----------------------------------

MvKeyProfile::MvKeyProfile(const MvKeyProfile& copy)
{
    name_          = copy.name_;
    systemProfile_ = copy.systemProfile_;
    for (auto it : copy) {
        MvKey* key = it->clone();
        push_back(key);
    }
    errorRows_ = copy.errorRows_;
}

MvKeyProfile::~MvKeyProfile()
{
    clear();
}

MvKeyProfile* MvKeyProfile::clone()
{
    auto* cp = new MvKeyProfile(*this);
    return cp;
}

void MvKeyProfile::clear()
{
    for (auto& it : *this) {
        delete it;
    }
    std::vector<MvKey*>::clear();
    errorRows_.clear();
}

void MvKeyProfile::addKey(MvKey* key)
{
    push_back(key);
}

MvKey* MvKeyProfile::addKey()
{
    auto* key = new MvKey;
    push_back(key);
    return key;
}

void MvKeyProfile::insertKey(int before, MvKey* key)
{
    if (before >= static_cast<int>(size()))
        push_back(key);
    else if (before <= 0)
        insert(begin(), key);
    else {
        auto it = begin() + before;
        insert(it, key);
    }
}

void MvKeyProfile::deleteKey(int index)
{
    auto it = begin() + index;
    delete (*it);
    erase(it);
}

void MvKeyProfile::swap(int i, int j)
{
    std::swap(at(i), at(j));
}

void MvKeyProfile::reposition(int indexFrom, int indexTo)
{
    if (indexFrom == indexTo)
        return;

    if (indexFrom > indexTo) {
        for (int i = indexFrom; i > indexTo; i--) {
            std::swap(at(i), at(i - 1));
        }
    }
    else {
        for (int i = indexFrom; i < indexTo; i++) {
            std::swap(at(i), at(i + 1));
        }
    }
}

void MvKeyProfile::reposition(const std::vector<int>& pos)
{
    if (size() != pos.size())
        return;

    //Create a copy
    std::vector<MvKey*> profCopy;
    for (size_t i = 0; i < size(); i++)
        profCopy.push_back(at(i));

    for (size_t i = 0; i < size(); i++)
        (*this)[i] = profCopy[pos[i]];
}

MvKey* MvKeyProfile::key(std::string keyName) const
{
    for (auto it : *this) {
        if (it->name() == keyName) {
            return it;
        }
    }
    return nullptr;
}

MvKey* MvKeyProfile::key(MvKey::Role role) const
{
    for (auto it : *this) {
        if (it->role() == role) {
            return it;
        }
    }
    return nullptr;
}

MvKey* MvKeyProfile::key(MvKey::Role role, int occurence) const
{
    int cnt = 0;
    for (auto it : *this) {
        if (it->role() == role) {
            if (cnt == occurence)
                return it;
            cnt++;
        }
    }
    return nullptr;
}


int MvKeyProfile::valueNum(int index)
{
    if (index >= 0 && index < static_cast<int>(size())) {
        return at(index)->value().size();
    }
    return 0;
}

int MvKeyProfile::valueNum() const
{
    std::size_t num = size();
    for (std::size_t i = 0; i < num; i++) {
        MvKey* k = at(i);
        if (!k->isConstant()) {
            return k->valueNum();
        }
    }

    if (size() == 0)
        return 0;

    //everything is constant
    return 1;
}

void MvKeyProfile::clearKeyData()
{
    for (auto& it : *this) {
        it->clearData();
    }
    errorRows_.clear();
}

void MvKeyProfile::expand(std::vector<MvKeyProfile*> ref)
{
    for (auto& it : *this) {
        const std::string& name = it->name();
        for (auto profIt : ref) {
            if (MvKey* k = profIt->key(name)) {
                delete it;
                it = k->clone();
                break;
            }
        }
    }
}

bool MvKeyProfile::isErrorRow(int row) const
{
    size_t n = errorRows_.size();
    for (size_t i = 0; i < n; i++)
        if (errorRows_[i] == row)
            return true;

    return false;
}

void MvKeyProfile::preAllocate(int num)
{
    assert(num >= 0);
    for (size_t i = 0; i < size(); i++) {
        at(i)->preAllocate(num);
    }
}

void MvKeyProfile::setValuesInChunk(int chunkStart, int chunkSize, MvKeyProfile* chunkProf)
{
    assert(chunkProf);
    assert(chunkStart >= 0);
    //assert(chunkStart+chunkSize < )
    assert(size() == chunkProf->size());

    for (int& errorRow : chunkProf->errorRows_) {
        errorRows_.push_back(errorRow);
    }

    for (size_t i = 0; i < size(); i++) {
        if (at(i)->name() != "MV_Index")
            at(i)->setValue(chunkStart, chunkSize, chunkProf->at(i));
    }
}

const std::string& MvKeyProfile::metaData(const std::string& key) const
{
    static std::string es;
    auto it = metaData_.find(key);
    return (it != metaData_.end()) ? it->second : es;
}

void MvKeyProfile::setMetaData(const std::string& key, const std::string& value)
{
    metaData_[key] = value;
}


void MvKeyProfile::clearMetaData()
{
    metaData_.clear();
}

bool MvKeyProfile::operator==(const MvKeyProfile& o) const
{
    if (o.size() != size())
        return false;

    for (size_t i = 0; i < size(); i++) {
        if (*at(i) != *(o.at(i)))
            return false;
    }

    return true;
}

bool MvKeyProfile::operator!=(const MvKeyProfile& p) const
{
    return !(p == *this);
}
