#include "AdvConvolutionBase.hh"

const std::string AdvConvolutionBase::className("AdvConvolutionBase");

AdvConvolutionBase::AdvConvolutionBase() {
}

AdvConvolutionBase::AdvConvolutionBase(const std::string name) : AdvMultiDataMethod(name) {
}

AdvConvolutionBase::~AdvConvolutionBase() {
}

AdvParamSet AdvConvolutionBase::setDefaultParam(ElementContainerArray& src) {
    AdvParamSet *retval=new AdvParamSet();

    retval->add(AdvConvolutionBase::RES_INDEX,        AdvConvolutionBase::DEFAULT_RES_INDEX);
    retval->add(AdvConvolutionBase::WIDTH_THRESHOLD,  AdvConvolutionBase::DEFAULT_WIDTH_THRESHOLD);
    //retval->add(AdvConvolutionBase::CONVOLUTION_TYPE, AdvConvolutionBase::DEFAULT_CONVOLUTION_TYPE);
    setDefaultConvolutionType(*retval);

    return *retval;
}

Bool AdvConvolutionBase::checkBinWidth(const std::vector<Double>& bin, const Double widthThreshold) {
    Double width = (bin.back() - bin.front())/(bin.size()-1);
    Bool retval = true;
    for (UInt4 i=0; i<bin.size()-1; ++i) {
        retval = retval && (abs((bin.at(i+1) - bin.at(i))/width -1) < widthThreshold);
    }
    return retval;
}

Bool AdvConvolutionBase::checkParam(ElementContainerArray& src, std::vector<AdvDomain>& domains, AdvParamSet& paramSet) {
    std::string memberName("checkParam(ElementContainer&, std::vector<AdvDomain>&, AdvParamSet&)");
    Bool retval=true;

    if (src.PutSize() <= 1) {
        retval=false;
        errorMessage(className, memberName, __FILE__, __LINE__, "too few source data\n");
    }

    UInt4 resID=paramSet.getUInt4(AdvConvolutionBase::RES_INDEX);
    if ( ! (0 <= resID && resID < src.PutSize())) {
        retval=false;
        errorMessage(className, memberName, __FILE__, __LINE__, "too few source data\n");
    }

    Double widthThreshold=paramSet.getDouble(AdvConvolutionBase::WIDTH_THRESHOLD);
    for (UInt4 i=0; i<src.PutSize(); ++i) {
        std::vector<Double> bin=src.PutPointer(i)->PutX();
        if (! this->checkBinWidth(bin, widthThreshold)) {
            retval=false;
            errorMessage(className, memberName, __FILE__, __LINE__, "the width of bins of the %d-th. data are not constant.\n");
        }
    }

    if (! checkConvolutionType(paramSet)) {
        warningMessage(className, memberName, __FILE__, __LINE__, "ignore the given values: %s\n");
    }
   
    return retval;
}

void AdvConvolutionBase::importRes(ElementContainer* ec, AdvDomain& domain) {
    this->resBin=ec->PutX();

    this->resY  =ec->PutY();
    this->resErr=ec->PutE();
}

void AdvConvolutionBase::importSrc(ElementContainerArray& src, std::vector<AdvDomain>& domains) {
    this->dataLengthMap.clear();
    for (UInt4 i=0; i<src.PutSize(); ++i) {
        if (i==this->resId) continue;

        this->srcBins.push_back(src.PutPointer(i)->PutX());
        this->srcYs.push_back(  src.PutPointer(i)->PutY());
        this->srcErrs.push_back(src.PutPointer(i)->PutE());

        if (this->dataLengthMap.find(this->srcYs.back().size()) != this->dataLengthMap.end()) {
            this->dataLengthMap.insert(make_pair(this->srcYs.back().size(), std::vector<UInt4>()));
        }
        this->dataLengthMap[this->srcYs.back().size()].push_back(i < this->resId ? i : i-1);
    }

    this->resultBins.assign(this->srcBins.begin(), this->srcBins.end());
    this->resultYs.assign(this->srcYs.size(), std::vector<Double>());
    this->resultErrs.assign(this->srcYs.size(), std::vector<Double>());

}

void AdvConvolutionBase::toInnerForm(ElementContainerArray& src, std::vector<AdvDomain>& domains, AdvParamSet& paramSet) {
    this->resId = paramSet.getUInt4(AdvConvolutionBase::RES_INDEX);
    this->importRes(src.PutPointer(this->resId), domains.at(this->resId));
    this->importSrc(src, domains);
}

void AdvConvolutionBase::toElementContainer(ElementContainerArray& src, UInt4 i, ElementContainer& dest) const {
    std::string xkey  = src.PutPointer(i)->PutXKey();
    std::string ykey  = src.PutPointer(i)->PutYKey();
    std::string ekey  = src.PutPointer(i)->PutEKey();
    std::string xunit = src.PutPointer(i)->PutUnit(xkey);
    std::string yunit = src.PutPointer(i)->PutUnit(ykey);
    std::string eunit = src.PutPointer(i)->PutUnit(ekey);

    if ( i < this->resId ) {
        dest.Add(xkey, this->resultBins.at(i), xunit);
        dest.Add(ykey, this->resultYs.at(i),   yunit);
        dest.Add(ekey, this->resultErrs.at(i), eunit);
        dest.SetKeys(xkey,ykey,ekey);
    } else if ( i == this->resId ) {
        dest.Add(xkey, this->resBin, xunit);
        dest.Add(ykey, this->resY,   yunit);
        dest.Add(ekey, this->resErr, eunit);
        dest.SetKeys(xkey,ykey,ekey);
    } else {
        dest.Add(xkey, this->resultBins.at(i - 1), xunit);
        dest.Add(ykey, this->resultYs.at(i - 1),   yunit);
        dest.Add(ekey, this->resultErrs.at(i - 1), eunit);
        dest.SetKeys(xkey,ykey,ekey);
    }
}

void AdvConvolutionBase::toElementContainerArray(ElementContainerArray& src, ElementContainerArray& dest) const {

    for (UInt4 i=0; i<src.PutSize(); ++i) {
        ElementContainer *ec = new ElementContainer(src.PutPointer(i)->PutHeader());
        this->toElementContainer(src, i, *ec);
        dest.AddPointer(ec);
    }
}
