#include "AdvSimulatedAnnealingArgs.hh"

#include <cfloat>

const std::string AdvSimulatedAnnealingArgs::className=std::string("AdvSimulatedAnnealingArgs");

/**
 *  constructor
 */
AdvSimulatedAnnealingArgs::AdvSimulatedAnnealingArgs() {
    funcList.clear();
    funcMatrix.clear();
    nParam = 0;
    nRef.clear();
    param.clear();
    cur_param.clear();
    n_data = 0;
    ref.clear();
    err.clear();
    srcBin.clear();
    srcX.clear();
    best_param.clear();
    bestY.clear();

    ncall = 0;
    min_chi2 = DBL_MAX;

    lb.clear();
    ub.clear();

    constrain      = AdvSimulatedAnnealingConsts::DEFAULT_CONSTRAIN;
    useDataWeights = AdvSimulatedAnnealingConsts::DEFAULT_USE_DATA_WEIGHTS;
    outputInterval = AdvSimulatedAnnealingConsts::DEFAULT_OUTPUT_INTERVAL;

    gsl_rng_env_setup();
    rand = gsl_rng_alloc(gsl_rng_default);

    siman_params.n_tries         = AdvSimulatedAnnealingConsts::DEFAULT_N_TRIES;
    siman_params.iters_fixed_T   = AdvSimulatedAnnealingConsts::DEFAULT_ITERS_FIXED_T;
    siman_params.step_size       = AdvSimulatedAnnealingConsts::DEFAULT_STEP_SIZE;
    siman_params.k               = AdvSimulatedAnnealingConsts::DEFAULT_K;
    siman_params.t_initial       = AdvSimulatedAnnealingConsts::DEFAULT_T_INITIAL;
    siman_params.mu_t            = AdvSimulatedAnnealingConsts::DEFAULT_MU_T;
    siman_params.t_min           = AdvSimulatedAnnealingConsts::DEFAULT_T_MIN;
}

/**
 *  destructor
 */
AdvSimulatedAnnealingArgs::~AdvSimulatedAnnealingArgs() {
    std::string memberName=std::string("~AdvSimulatedAnnealingArgs");
    DebugMessage(className, memberName, "enter\n");

    gsl_rng_free(rand);

    DebugMessage(className, memberName, "exit\n");
}

/**
 *  check the consistency of the input parameters
 *
 *  \param[in] src    an element container as the source data
 *  \param[in] domain the domain for fitting
 *  \param[in] param  parameter container for fitting
 */
Bool AdvSimulatedAnnealingArgs::checkParam(ElementContainer &src, AdvDomain &domain, AdvParamSet &param) {
    std::string memberName = std::string("checkParam(ElementContainer &, AdvDomain &, AdvParamSet &)");
    DebugMessage(className, memberName, "enter\n");

    Bool ret1 = checkConstrainType(param);
    Bool ret2 = checkUseDataWeights(param);
    Bool ret3 = checkNumberOfFittingParam(param);

    Bool ret4 = true;
    AdvSimulatedAnnealingConsts::Constrain constrain = getConstrain(param);
    switch (constrain) {
        case AdvSimulatedAnnealingConsts::BOX:
            ret4 = checkBoxies(param);
            break;
        default:
            break;
    }

    DebugMessage(className, memberName, "exit\n");
    return ret1 && ret2 && ret3 && ret4 ;
}

Bool AdvSimulatedAnnealingArgs::checkParam(ElementContainerArray &src, std::vector<AdvDomain> &domainArray, AdvParamSet &param) {
    std::string memberName = std::string("checkParam(ElementContainerArray &, std::vector<AdvDomain> &, AdvParamSet &)");
    DebugMessage(className, memberName, "enter\n");

    if (src.PutSize() < 1) return false;
    if (domainArray.size() < 1) return false;

    ElementContainer elem = src.Put(0);
    AdvDomain  domain = domainArray[0];

    Bool  ret = checkParam(elem, domain, param);
    DebugMessage(className, memberName, "exit\n");
    return ret;
}

/**
 *  check Levmar constrain type in a parameter container
 * 
 *  \param[in] param a paameter container
 *  \return true, if the parameter container containes Levmar constrain type
 */
Bool AdvSimulatedAnnealingArgs::checkConstrainType(const AdvParamSet &param) {
    std::string memberName=std::string("checkConstrainType(AdvParamSet &)");
    DebugMessage(className, memberName, "enter\n");

    std::string key = AdvSimulatedAnnealingConsts::CONSTRAIN;

    if ( ! param.contain(key) ) {
        errorMessage(className, memberName, "does not contain the parameter with the key \"%s\"\n", key.c_str());
        return false;
    } else {
        try {
            // AdvSimulatedAnnealingConsts::Constrain constrain = getConstrain(param);
            getConstrain(param);
        } catch (std::exception &e) {
            errorMessage(className, memberName, "fail to get constrain type.\n");
            return false;
        }
    }
    DebugMessage(className, memberName, "exit\n");
    return true;
}

/**
 *  check the flag to use fitting with weight
 *
 *  \param[in] param a paameter container
 *  \return true, if the parameter container contains the flag
 */
Bool AdvSimulatedAnnealingArgs::checkUseDataWeights(const AdvParamSet &param) {
    std::string memberName=std::string("checkUseDataWeights(AdvParamSet &)");
    DebugMessage(className, memberName, "enter\n");
    std::string key = AdvSimulatedAnnealingConsts::USE_DATA_WEIGHTS;

    if ( ! param.contain(AdvSimulatedAnnealingConsts::USE_DATA_WEIGHTS) ) {
        errorMessage(className, memberName, "does not contain the parameter with the key \"%s\"\n", key.c_str());
        return false;
    }
    DebugMessage(className, memberName, "exit\n");
    return true;
}

/**
 *  check the number of fitting parameters (the length  of a vector for fitting parameters)
 *
 *  \param[in] param a parameter container
 */
Bool AdvSimulatedAnnealingArgs::checkNumberOfFittingParam(const AdvParamSet &param) {
    std::string memberName=std::string("checkNumberOfFittingParam(AdvParamSet &)");
    DebugMessage(className, memberName, "enter\n");
    std::string key   =AdvSimulatedAnnealingConsts::PARAMETER_VALUES;
    std::string relkey=AdvSimulatedAnnealingConsts::FUNCTIONS;

    if ( ! param.contain(key) ) {
        errorMessage(className, memberName, "does not contain the parameter with the key \"%s\"\n", key.c_str());
        return false;
    }
    if ( ! param.contain(key) ) {
        errorMessage(className, memberName, "does not contain the parameter with the key \"%s\"\n", relkey.c_str());
        return false;
    }
    Int4 nParam = setNumberOfFittingParam(param);
    Int4 nReq   = setNumberOfRequiredParam(param);
    if ( nParam != nReq ) {
        errorMessage(className, memberName, "the number of the fitting parmeters in not equal to the number of paramters that the fitting functions require.");
        errorMessage(className, memberName, "the number of the fitting parameters:  %d\n", nParam);
        errorMessage(className, memberName, "the number of the required parameters: %d\n", nReq  );
        return false;
    }
    DebugMessage(className, memberName, "exit\n");
    return true;
}

/**
 *  check the constrain box(ies), i.e.  vectors for the lower and upper bounds.
 *
 *  \param[in] param a parameter container
 *  \retvrn true, if the parameter container contains required vectors
 */
Bool AdvSimulatedAnnealingArgs::checkBoxies(const AdvParamSet &param) {
    std::string memberName=std::string("checkBoxies(AdvParamSet &)");
    DebugMessage(className, memberName, "enter\n");
    std::string key1  =AdvSimulatedAnnealingConsts::LOWER_BOUNDS;
    std::string key2  =AdvSimulatedAnnealingConsts::UPPER_BOUNDS;
    std::string relKey=AdvSimulatedAnnealingConsts::FUNCTIONS;

    if ( ! param.contain(key1) ) {
        errorMessage(className, memberName, "does not contain the parameter with the key \"%s\"\n", key1.c_str());
        return false;
    }
    if ( ! param.contain(key2) ) {
        errorMessage(className, memberName, "does not contain the parameter with the key \"%s\"\n", key2.c_str());
        return false;
    }
    if ( ! param.contain(relKey) ) {
        errorMessage(className, memberName, "does not contain the parameter with the key \"%s\"\n", relKey.c_str());
        return false;
    }
    Int4 nlb =param.getVectorSize(key1);
    Int4 nub =param.getVectorSize(key2);
    Int4 nReq=setNumberOfRequiredParam(param);
    if ( nlb < nReq ) {
        errorMessage(className, memberName, "the number of %s is not equal to the number of parameters that functions required\n", key1.c_str());
        errorMessage(className, memberName, "the number of the %s : %u ", key1.c_str(), nlb);
        errorMessage(className, memberName, "the number of the required paameter : %u ", nReq);
        return false;
    }
    if ( nub < nReq ) {
        errorMessage(className, memberName, "the number of %s is not equal to the number of parameters that functions required\n", key2.c_str());
        errorMessage(className, memberName, "the number of the %s : %u ", key2.c_str(), nub);
        errorMessage(className, memberName, "the number of the required paameter : %u ", nReq);
        return false;
    }
    DebugMessage(className, memberName, "exit\n");
    return true;
}

void AdvSimulatedAnnealingArgs::toInnerForm(const AdvParamSet &param) {
    funcList         = setFuncList(param);
    funcMatrix       = setFuncMatrix(param);
    nParam           = setNumberOfFittingParam(param);
    this->param      = param.getVector(AdvSimulatedAnnealingConsts::PARAMETER_VALUES);
    cur_param        = this->param;
    best_param       = this->param;
    useDataWeights   = param.getBool(AdvSimulatedAnnealingConsts::USE_DATA_WEIGHTS);
    constrain        = getConstrain(param);
    if (constrain==AdvSimulatedAnnealingConsts::BOX) {
        lb           = param.getVector(AdvSimulatedAnnealingConsts::LOWER_BOUNDS);
        ub           = param.getVector(AdvSimulatedAnnealingConsts::UPPER_BOUNDS);
    }
    outputInterval   = param.getInt4(AdvSimulatedAnnealingConsts::OUTPUT_INTERVAL);
    siman_params.n_tries         =     param.getInt4  (AdvSimulatedAnnealingConsts::N_TRIES);
    siman_params.iters_fixed_T   =     param.getInt4  (AdvSimulatedAnnealingConsts::ITERS_FIXED_T);
    siman_params.step_size       =     param.getDouble(AdvSimulatedAnnealingConsts::STEP_SIZE);
    siman_params.k               =     param.getDouble(AdvSimulatedAnnealingConsts::K);
    siman_params.t_initial       =     param.getDouble(AdvSimulatedAnnealingConsts::T_INITIAL);
    siman_params.mu_t            =     param.getDouble(AdvSimulatedAnnealingConsts::MU_T);
    siman_params.t_min           =     param.getDouble(AdvSimulatedAnnealingConsts::T_MIN);
}

void AdvSimulatedAnnealingArgs::toInnerForm(ElementContainer &src, AdvDomain &domain, AdvParamSet &param) {
    std::string memberName=std::string("toInnerForm(ElementContainer &, AdvDomain &, AdvParamSet &)");
    DebugMessage(className, memberName, "enter\n");
    DebugMessage(className, memberName, "domain is ignored.\n");

    toInnerForm(param);

    std::string xKey=src.PutXKey();
    std::string yKey=src.PutYKey();
    std::string eKey=src.PutEKey();

    n_data = 1;

    srcBin.resize(1);
    srcX.resize(1);
    ref.resize(1);
    err.resize(1);
    nRef.resize(1);

    srcBin[0] = src.Put(xKey);

    srcX[0].clear();
    for (UInt4 i=0; i<srcBin[0].size()-1; ++i) {
        srcX[0].push_back( (src.Put(xKey, i)+src.Put(xKey, i+1))/2.0 );
    }

    nRef[0]= srcX[0].size();
    ref[0] = src.Put(yKey);
    err[0] = src.Put(eKey);

    DebugMessage(className, memberName, "exit\n");
}

void AdvSimulatedAnnealingArgs::toInnerForm(ElementContainerArray &src, std::vector<AdvDomain> &domain, AdvParamSet &param) {
    std::string memberName=std::string("toInnerForm(ElementContainer &, AdvDomain &, AdvParamSet &)");
    DebugMessage(className, memberName, "enter\n");
    DebugMessage(className, memberName, "domain is ignored.\n");

    toInnerForm(param);

    n_data = src.PutSize();
    if (n_data<2) {
        std::printf("Error: toInnerForm: ElementContainerArray contains less than 2 ElementContainer\n");
        return;
    }

    srcBin.resize(n_data);
    srcX.resize(n_data);
    nRef.resize(n_data);
    ref.resize(n_data);
    err.resize(n_data);

    for (Int4 i=0; i<n_data; ++i) {
        ElementContainer* elem = src.PutPointer(i);

        std::string xKey=elem->PutXKey();
        std::string yKey=elem->PutYKey();
        std::string eKey=elem->PutEKey();

        srcBin[i] = elem->Put(xKey);

        srcX[i].clear();
        for (UInt4 j=0; j<srcBin[i].size()-1; ++j) {
            srcX[i].push_back( (elem->Put(xKey, j)+elem->Put(xKey, j+1))/2.0 );
        }

        nRef[i]= srcX[i].size();
        ref[i] = elem->Put(yKey);
        err[i] = elem->Put(eKey);
    }

    DebugMessage(className, memberName, "exit\n");
}

/**
 *  get Levmar constrain type from a parameter container
 *
 *  \param[in] param a paameter container
 */
AdvSimulatedAnnealingConsts::Constrain AdvSimulatedAnnealingArgs::getConstrain(const AdvParamSet &param) {
    return static_cast<AdvSimulatedAnnealingConsts::Constrain>(param.getInt4(AdvSimulatedAnnealingConsts::CONSTRAIN));
}

/**
 *  get a list of fitting functions from a parameter container
 *
 *  \param[in] param a parameter conatainer
 */
std::vector<AdvFuncBase*> AdvSimulatedAnnealingArgs::setFuncList(const AdvParamSet &param) {
    return param.getFuncList(AdvSimulatedAnnealingConsts::FUNCTIONS);
}

std::vector< std::vector<AdvFuncBase*> > AdvSimulatedAnnealingArgs::setFuncMatrix(const AdvParamSet &param) {
    return param.getFuncMatrix(AdvSimulatedAnnealingConsts::FUNCTIONS);
}

/**
 *  get the number of fitting parameters
 *
 *  \param[in] param a parameter conatainer
 */
Int4 AdvSimulatedAnnealingArgs::setNumberOfFittingParam(const AdvParamSet &param) {
    return param.getVectorSize(AdvSimulatedAnnealingConsts::PARAMETER_VALUES);
}


/**
 *  get the number of parameters that the fitting function required
 *
 *  \param[in] param a parameter conatainer
 */
Int4 AdvSimulatedAnnealingArgs::setNumberOfRequiredParam(const AdvParamSet &param) {
    std::string memberName=std::string("getNumberOfRequiredParam(AdvParamSet &)");
    DebugMessage(className, memberName, "enter\n");

    Int4 sum=0;
    std::vector<AdvFuncBase*> funcList=setFuncList(param);

    if (funcList.size() > 0) {

      for (std::vector<AdvFuncBase*>::const_iterator fp=funcList.begin(); fp != funcList.end(); ++fp) {
          sum += (*fp)->getNumberOfParam();
      }

      DebugMessage(className, memberName, "exit\n");
      return sum;

    }

    std::vector< std::vector<AdvFuncBase*> >  funcMatrix=setFuncMatrix(param);

std::cout << "funcMatrix.size() = " << funcMatrix.size() << std::endl;
    if (funcMatrix.size() > 0){

      Int4 sum0=0;
      for (std::vector<std::vector<AdvFuncBase*> >::const_iterator fpm=funcMatrix.begin(); fpm != funcMatrix.end(); ++fpm) {
        sum = 0;
        for (std::vector<AdvFuncBase*>::const_iterator fp=fpm->begin(); fp != fpm->end(); ++fp) {
std::cout << "(*fp)->getNumberOfParam() = " << (*fp)->getNumberOfParam() << std::endl;
          sum += (*fp)->getNumberOfParam();
        }
        if (sum0 == 0) {
          sum0 = sum;
        } else {
          if (sum0 != sum) {
            std::cout << "Error: Numbers of parameters are different in multi-data multi-function fittion" << std::endl;
            return 0;
          }
        }
      }

      DebugMessage(className, memberName, "exit\n");
      return sum0;

    }

    std::cout << "No FUNCTIONS are defined in parameter set." << std::endl;

    DebugMessage(className, memberName, "exit\n");
    return 0;
}


/**
 *  set default values of parameters for the method
 */
AdvParamSet AdvSimulatedAnnealingArgs::setDefaultParam(ElementContainer &src) {
    std::string memberName = std::string("setDefaultParam(ElementContainer &)");
    DebugMessage(className, memberName, "enter\n");

    AdvParamSet param;

    /* constrain type */
    param.add(AdvSimulatedAnnealingConsts::CONSTRAIN,          AdvSimulatedAnnealingConsts::DEFAULT_CONSTRAIN);
    param.add(AdvSimulatedAnnealingConsts::USE_DATA_WEIGHTS,   AdvSimulatedAnnealingConsts::DEFAULT_USE_DATA_WEIGHTS);

    /* output interval */
    param.add(AdvSimulatedAnnealingConsts::OUTPUT_INTERVAL,    AdvSimulatedAnnealingConsts::DEFAULT_OUTPUT_INTERVAL);

    param.add(AdvSimulatedAnnealingConsts::N_TRIES,            AdvSimulatedAnnealingConsts::DEFAULT_N_TRIES);
    param.add(AdvSimulatedAnnealingConsts::ITERS_FIXED_T,      AdvSimulatedAnnealingConsts::DEFAULT_ITERS_FIXED_T);
    param.add(AdvSimulatedAnnealingConsts::STEP_SIZE,          AdvSimulatedAnnealingConsts::DEFAULT_STEP_SIZE);
    param.add(AdvSimulatedAnnealingConsts::K,                  AdvSimulatedAnnealingConsts::DEFAULT_K);
    param.add(AdvSimulatedAnnealingConsts::T_INITIAL,          AdvSimulatedAnnealingConsts::DEFAULT_T_INITIAL);
    param.add(AdvSimulatedAnnealingConsts::MU_T,               AdvSimulatedAnnealingConsts::DEFAULT_MU_T);
    param.add(AdvSimulatedAnnealingConsts::T_MIN,              AdvSimulatedAnnealingConsts::DEFAULT_T_MIN);

    DebugMessage(className, memberName, "exit\n");
    return param;
}

AdvParamSet AdvSimulatedAnnealingArgs::getBestParam() const {
    AdvParamSet fittedParam;
    std::vector<Double> v;

    fittedParam.add(AdvSimulatedAnnealingConsts::CONSTRAIN,          constrain);
    fittedParam.add(AdvSimulatedAnnealingConsts::USE_DATA_WEIGHTS,   useDataWeights);

    fittedParam.add(AdvSimulatedAnnealingConsts::OUTPUT_INTERVAL,    outputInterval);

    fittedParam.add(AdvSimulatedAnnealingConsts::FUNCTIONS,          funcList);

    v = param;
    fittedParam.add(AdvSimulatedAnnealingConsts::INITIAL_PARAM_VALUES, v);
    v = best_param;
    fittedParam.add(AdvSimulatedAnnealingConsts::PARAMETER_VALUES,     v);

//    fittedParam.add(AdvSimulatedAnnealingConsts::PARAM_ERRORS, v);


    return fittedParam;
}
