#include "AxisLegendManager.hh"
#include <iostream>       // std::cout
#include <string> 
#include <vector>

using namespace AMDA::Info;

namespace plot {

    void AxisLegendManager::configureXAxisLegendForSeries(
            XYPlot* plot) {
        // Build list of all indexes used by each parameters for each x axes
        std::map<std::string, AxisParamsComponents> axesParamsComponents;

        // Compute nb series to draw by y axis
        std::map<std::string, int> nbSeriesByYAxisMap = plot->getNbSeriesByYAxis();

        SeriesProperties lSeriesProperties;
        for (auto param : plot->_parameterAxesList) {
            if (param.getIntervalsProperties() != nullptr)
            {
                std::cout << std::endl
                          << std::endl
                          << std::endl
                          << std::endl
                          << param.getIntervalsProperties()->getColor() << std::endl
                          << std::endl
                          << std::endl;
            }
            for (auto lSeriesProperties : param.getYSeriePropertiesList()) {
                if (!lSeriesProperties.hasYAxis() || !lSeriesProperties.hasXAxis())
                    continue;

                bool moreThanOneSerieForAxis = (nbSeriesByYAxisMap[lSeriesProperties.getYAxisId()] > 1);

                ParameterAxes* xParameter = plot->getParameterAxesByXSerieId(lSeriesProperties.getXId());

                XSeriesProperties xSerie = xParameter->getXSeriePropertiesById(lSeriesProperties.getXId());

                std::string xAxisId = lSeriesProperties.getXAxisId();

                boost::shared_ptr<Axis> lXAxis = plot->_panel->getAxis(xAxisId);
                if (lXAxis.get() == nullptr) {
                    continue;
                }

                Color compLegendColor = lXAxis->_color;
                if (moreThanOneSerieForAxis && (lSeriesProperties.getColorSerieId() == -1) && (!plot->_panel->_page->_superposeMode)) {
                    compLegendColor = plot->getSerieLineColor(lSeriesProperties, moreThanOneSerieForAxis);
                }

                ParameterIndexComponentColor xIndex = ParameterIndexComponentColor(xSerie.getIndex(), compLegendColor);
                pushComponentInList(xSerie.getParamId(), xIndex, axesParamsComponents[xAxisId]);
            }
        }

        if (!plot->_panel->_page->_superposeMode) {
            plot->resetAutomaticSerieColorCursor();
        }

        std::list<std::string> legendLines;
        for (auto axisParamsComponents : axesParamsComponents) {
            boost::shared_ptr<Axis> lXAxis = plot->_panel->getAxis(axisParamsComponents.first);
            setAxisLegendForSeries(plot, lXAxis, axisParamsComponents.second);
        }
    }

    void AxisLegendManager::configureYAxisLegendForSpectro(
            PanelPlotOutput* plot) {
        for (auto param : plot->_parameterAxesList) {
            std::shared_ptr<SpectroProperties> spectroPropertiesPtr = param.getSpectroProperties();
            if (spectroPropertiesPtr == nullptr)
                continue; //no spectro defined

            if (!spectroPropertiesPtr->hasYAxis())
                continue;

            std::string yAxisId = spectroPropertiesPtr->getYAxisId();
            boost::shared_ptr<Axis> lYAxis = plot->_panel->getAxis(yAxisId);
            if (lYAxis.get() == nullptr)
                continue;

            if (!lYAxis->_legend.isEmpty())
                continue;

            setAxisLegendForTable(plot, lYAxis, param._originalParamId, spectroPropertiesPtr->getParamId(), spectroPropertiesPtr->getIndexes(), spectroPropertiesPtr->getRelatedDim());
            return;
        }
    }

    void AxisLegendManager::configureYAxisLegendForSeries(
            PanelPlotOutput* plot) {
        SeriesProperties lSeriesProperties;

        // Compute nb series to draw by y axis
        std::map<std::string, int> nbSeriesByYAxisMap = plot->getNbSeriesByYAxis();

        // Build list of all indexes used by each parameters for each y axes
        std::map<std::string, AxisParamsComponents> axesParamsComponents;
        if (!plot->_panel->_page->_superposeMode) {
            plot->resetAutomaticSerieColorCursor();
        }
        for (auto param : plot->_parameterAxesList) {
            for (auto lSeriesProperties : param.getYSeriePropertiesList()) {
                if (!lSeriesProperties.hasYAxis())
                    continue;

                bool moreThanOneSerieForAxis = (nbSeriesByYAxisMap[lSeriesProperties.getYAxisId()] > 1);

                std::string yAxisId = lSeriesProperties.getYAxisId();
                boost::shared_ptr<Axis> lYAxis = plot->_panel->getAxis(yAxisId);
                if (lYAxis.get() == nullptr) {
                    continue;
                }

                for (auto index : lSeriesProperties.getIndexList(plot->_pParameterValues)) {
                    Color compLegendColor = lYAxis->_color;
                    if (moreThanOneSerieForAxis && (lSeriesProperties.getColorSerieId() == -1) && (!plot->_panel->_page->_superposeMode)) {
                        compLegendColor = plot->getSerieLineColor(lSeriesProperties, moreThanOneSerieForAxis);
                    }
                    ParameterIndexComponentColor yIndex = ParameterIndexComponentColor(index, compLegendColor);
                    pushComponentInList(lSeriesProperties.getParamId(), yIndex, axesParamsComponents[yAxisId]);
                }
            }
        }
        if (!plot->_panel->_page->_superposeMode) {
            plot->resetAutomaticSerieColorCursor();
        }

        std::list<std::string> legendLines;
        for (auto axisParamsComponents : axesParamsComponents) {
            boost::shared_ptr<Axis> lYAxis = plot->_panel->getAxis(axisParamsComponents.first);
            setAxisLegendForSeries(plot, lYAxis, axisParamsComponents.second);
        }
    }

    void AxisLegendManager::configureColorAxisLegendForSeries(
            PanelPlotOutput* plot) {
        SeriesProperties lSeriesProperties;
        ColorSeriesProperties lColorSerieProperties;
        boost::shared_ptr<Axis> lZAxis = plot->_panel->getColorAxis();

        if (lZAxis.get() == nullptr) {
            return;
        }

        // Build list of all indexes used by each parameters for each y axes
        AxisParamsComponents axisParamsComponents;
        for (auto param : plot->_parameterAxesList) {
            for (auto lSeriesProperties : param.getYSeriePropertiesList()) {
                if (!lSeriesProperties.hasYAxis())
                    continue;

                std::string yParamId = lSeriesProperties.getParamId();

                //check if a colored param is defined for this serie
                if (lSeriesProperties.getColorParamId().empty())
                    continue;

                ParameterAxes* colorSerieParameterAxes = plot->getParameterAxesByColorSerieId(lSeriesProperties.getColorSerieId());

                if (colorSerieParameterAxes == NULL)
                    continue;

                lColorSerieProperties = colorSerieParameterAxes->getColorSeriePropertiesById(lSeriesProperties.getColorSerieId());

                ParameterIndexComponentColor colorSerieIndex = ParameterIndexComponentColor(-1, -1, lZAxis->_color);
                if (lColorSerieProperties.getIndex() > -1)
                    colorSerieIndex = ParameterIndexComponentColor(lColorSerieProperties.getIndex(), -1, lZAxis->_color);
                pushComponentInList(lColorSerieProperties.getColorParamIds()[yParamId], colorSerieIndex, axisParamsComponents);
            }
        }

        setAxisLegendForSeries(plot, lZAxis, axisParamsComponents);
    }

    void AxisLegendManager::configureColorAxisLegendForSpectro(
            PanelPlotOutput* plot) {
        boost::shared_ptr<Axis> lZAxis = plot->_panel->getColorAxis();

        if (lZAxis.get() == nullptr) {
            return;
        }

        for (auto param : plot->_parameterAxesList) {
            std::shared_ptr<SpectroProperties> spectroPropertiesPtr = param.getSpectroProperties();
            if (spectroPropertiesPtr == nullptr)
                continue; //no spectro defined

            if (!spectroPropertiesPtr->hasZAxis())
                continue;

            setAxisLegendForSpectro(plot, lZAxis, param._originalParamId);
            break;
        }
        
       for (auto param : plot->_parameterAxesList) {
            std::shared_ptr<SauvaudProperties> sauvaudPropertiesPtr = param.getSauvaudProperties();
            if (sauvaudPropertiesPtr == nullptr)
                continue; //no sauvaud defined

            if (!sauvaudPropertiesPtr->hasZAxis())
                continue;

            setAxisLegendForSpectro(plot, lZAxis, param._originalParamId);
            break;
        }
    }

    void AxisLegendManager::pushComponentInList(std::string paramId, ParameterIndexComponentColor& index, AxisParamsComponents& axisParamsComponents) {
        if (index.getDim1Index() == -1 && index.getDim2Index() == -1) {
            //All indexes of this parameter are used
            axisParamsComponents[paramId].clear();
            axisParamsComponents[paramId].push_back(index);
        } else {
            if (axisParamsComponents[paramId].size() > 0) {
                if (axisParamsComponents[paramId].front().getDim1Index() == -1 && axisParamsComponents[paramId].front().getDim2Index() == -1) {
                    //Skip. All components already defined for this paramter on this axis
                    return;
                }
            }
            for (auto crtIndex : axisParamsComponents[paramId]) {
                if ((crtIndex.getDim1Index() == index.getDim1Index()) && (crtIndex.getDim2Index() == index.getDim2Index())) {
                    //Component already exists
                    return;
                }
            }
            //Add this components for this axis
            axisParamsComponents[paramId].push_back(index);
        }

    }

    void AxisLegendManager::setAxisLegendForSeries(PanelPlotOutput* plot, boost::shared_ptr<Axis>& pAxis, AxisParamsComponents& axisParamsComponents) {
        if (pAxis == nullptr || !pAxis->_legend.isEmpty()) {
            return;
        }

        // Retrieve ParamInfo Manager
        ParamMgr* piMgr = ParamMgr::getInstance();

        for (auto paramsComponents : axisParamsComponents) {
            ParameterSPtr p = plot->_parameterManager.getParameter(paramsComponents.first);
            ParamInfoSPtr paramInfo = piMgr->getParamInfoFromId(p->getInfoId());
            if (paramsComponents.second.size() == p->getDataWriterTemplate()->getParamData()->getDim1() * p->getDataWriterTemplate()->getParamData()->getDim2()) {
                //All components of this parameter are used by this axis => merge
                Color color = paramsComponents.second.front().getColor();
                paramsComponents.second.clear();
                paramsComponents.second.push_back(ParameterIndexComponentColor(-1, -1, color));
            }
            bool isFirstComponent = true;
            for (auto components : paramsComponents.second) {
                if (paramInfo == nullptr)
                    continue;
                Label label(pAxis->_legend.getFont(), components.getColor());
                if (axisParamsComponents.size() == 1) {
                    if (isFirstComponent) {
                        std::string info = getMissionInstrumentInfo(paramInfo);
                        if (!info.empty()) {
                            label._text = info;
                            addBreakLine(label._text);
                        }
                    }
                } else {
                    std::string info = getMissionInfo(paramInfo);
                    if (!info.empty()) {
                        label._text = info + ", ";
                    }
                }
                label._text += getParamLegend(paramInfo, components);
                pAxis->_legend.pushLabel(label);
                isFirstComponent = false;
            }
        }
    }

    void AxisLegendManager::setAxisLegendForSpectro(PanelPlotOutput* plot, boost::shared_ptr<Axis>& pAxis, std::string originalParamId) {
        if (pAxis == nullptr || !pAxis->_legend.isEmpty()) {
            return;
        }

        // Retrieve ParamInfo Manager
        ParamMgr* piMgr = ParamMgr::getInstance();

        ParameterSPtr p = plot->_parameterManager.getParameter(originalParamId);
        AMDA::Info::ParamInfoSPtr paramInfo = piMgr->getParamInfoFromId(p->getInfoId());
        if (paramInfo == nullptr)
            return;

        Label label(pAxis->_legend.getFont(), pAxis->_legend.getColor());
        addInfoPart(label._text, paramInfo->getShortName());
        addInfoPart(label._text, getTransformedUnits(paramInfo->getUnits()));
        pAxis->_legend.setLabel(label);
    }

    void AxisLegendManager::setAxisLegendForTable(PanelPlotOutput* plot, boost::shared_ptr<Axis>& pAxis, std::string originalParamId, std::string paramId, AMDA::Common::ParameterIndexComponentList& indexes, int relatedDim) {
        if (pAxis == nullptr || !pAxis->_legend.isEmpty()) {
            return;
        }

        // Retrieve ParamInfo Manager
        ParamMgr* piMgr = ParamMgr::getInstance();

        ParameterSPtr p = plot->_parameterManager.getParameter(originalParamId);
        AMDA::Info::ParamInfoSPtr paramInfo = piMgr->getParamInfoFromId(p->getInfoId());

        if (paramInfo == nullptr)
            return;

        Label label(pAxis->_legend.getFont(), pAxis->_legend.getColor());
        std::string info = getMissionInstrumentInfo(paramInfo);
        if (!info.empty()) {
            addInfoPart(label._text, info);
            addBreakLine(label._text);
        }

        boost::shared_ptr<AMDA::Info::ParamTable> tableSPtr;
        tableSPtr = paramInfo->getTable(relatedDim);
        
           if (tableSPtr == nullptr){
            // look for unique embedded table
            boost::shared_ptr<AMDA::Info::ParamTable> linkedTableSPtr;
            int counter = 0;
            for (auto pInfo :paramInfo->getLinkedParamList()){
                linkedTableSPtr = AMDA::Info::ParamMgr::getInstance()->getParamInfoFromId(pInfo)->getTable(relatedDim);
                if (linkedTableSPtr == nullptr)
                    continue;
                counter ++;
            }
            if(linkedTableSPtr == nullptr || counter !=1){
                LOG4CXX_DEBUG(gLogger, "table cannot be defined from linked info params");
            }else{
                tableSPtr = linkedTableSPtr;
            }
         }

        if (tableSPtr == nullptr) {
            std::string tableInfo = "Table ";
            tableInfo += std::to_string(relatedDim);
            tableInfo += " indexes";
            addInfoPart(label._text, tableInfo);
            pAxis->_legend.pushLabel(label);
            return;
        }

        if (((*plot->_pParameterValues)[paramId].getDim1Size() > 0) &&
                ((*plot->_pParameterValues)[paramId].getDim2Size() > 0) &&
                !indexes.empty()) {
            boost::shared_ptr<AMDA::Info::ParamTable> otherTableSPtr;
            int otherDimIndex;
            if (relatedDim == 0) {
                otherTableSPtr = paramInfo->getTable(1);
                otherDimIndex = indexes.front().getDim2Index();
            } else {
                otherTableSPtr = paramInfo->getTable(0);
                otherDimIndex = indexes.front().getDim1Index();
            }
            if ((otherTableSPtr != nullptr) && !otherTableSPtr->isVariable(&plot->_parameterManager)) {
                if (otherTableSPtr->getName(&plot->_parameterManager).empty())
                    addInfoPart(label._text, "Table bounds");
                else
                    addInfoPart(label._text, otherTableSPtr->getName(&plot->_parameterManager));

                AMDA::Info::t_TableBound crtBound = otherTableSPtr->getBound(&plot->_parameterManager, otherDimIndex);
                addBoundsPart(label._text, crtBound.min, crtBound.max);
                std::string rawUnit = otherTableSPtr->getUnits(&plot->_parameterManager);
                addInfoPart(label._text, getTransformedUnits(rawUnit));
            } else {
                std::string part = "Table Index: ";
                part += std::to_string(otherDimIndex);
                addInfoPart(label._text, part);
            }
            addBreakLine(label._text);
        }
        addInfoPart(label._text, tableSPtr->getName(&plot->_parameterManager));
        std::string rawUnit = tableSPtr->getUnits(&plot->_parameterManager);
        addInfoPart(label._text, getTransformedUnits(rawUnit));
        pAxis->_legend.pushLabel(label);
    }

    void AxisLegendManager::addInfoPart(std::string& legend, std::string info) {
        if (!info.empty()) {
            if (!legend.empty()) {
                if (legend.length() >= Label::DELIMITER.length()) {
                    if (legend.compare(legend.length() - Label::DELIMITER.length(), Label::DELIMITER.length(), Label::DELIMITER) != 0) {
                        legend += ", ";
                    }
                } else {
                    legend += ", ";
                }
            }
            legend += info;
        }
    }

    void AxisLegendManager::addIndexPart(std::string& legend, ParameterIndexComponentColor& index) {
        legend += "[";
        legend += std::to_string(index.getDim1Index());
        if (index.getDim2Index() != -1) {
            legend += ",";
            legend += std::to_string(index.getDim2Index());
        }
        legend += "]";
    }

    void AxisLegendManager::addIndexPart(std::string& legend, int index) {
        legend += "[";
        legend += std::to_string(index);
        legend += "]";
    }

    void AxisLegendManager::addBoundsPart(std::string& legend, PLFLT min, PLFLT max) {
        legend += " ";

        char minCount[1024];
        char maxCount[1024];

        int precision = computeScientificFormatPrecision(min, max);

        getDigitalLabel(min, precision, minCount, 1024);
        getDigitalLabel(max, precision, maxCount, 1024);

        legend += std::string(minCount);
        legend += ", ";
        legend += std::string(maxCount);
    }

    void AxisLegendManager::addBreakLine(std::string& legend) {
        if (!legend.empty()) {
            legend += Label::DELIMITER;
        }
    }

    std::string AxisLegendManager::getParamLegend(AMDA::Info::ParamInfoSPtr paramInfo, ParameterIndexComponentColor& index) {
        std::string legend;
        addInfoPart(legend, paramInfo->getShortName());

        if ((index.getDim1Index() != -1) || (index.getDim2Index() != -1)) {
            AMDA::Common::ParameterIndexComponent paramIndex(index.getDim1Index(), index.getDim2Index());
            if (paramInfo->getComponents(paramIndex).empty() == false)
                addInfoPart(legend, paramInfo->getComponents(paramIndex));
            else {
                addInfoPart(legend, paramInfo->getShortName());
                addIndexPart(legend, index);
            }
        }

        addInfoPart(legend, getTransformedUnits(paramInfo->getUnits()));
        addInfoPart(legend, paramInfo->getCoordinatesSystem());

        return legend;
    }

    /*std::string AxisLegendManager::getParamLegend(AMDA::Info::ParamInfoSPtr paramInfo, int index) {
            std::string legend;
            addInfoPart(legend, paramInfo->getShortName());

            if ((index.getDim1Index() != -1) || (index.getDim2Index() != -1)) {
                    if (paramInfo->getComponents(index).empty() == false)
                            addInfoPart(legend, paramInfo->getComponents(index));
                    else
                    {
                            addInfoPart(legend, paramInfo->getShortName());
                            addIndexPart(legend, index);
                    }
            }

            addInfoPart(legend, paramInfo->getUnits());
            addInfoPart(legend, paramInfo->getCoordinatesSystem());

            return legend;
    }*/

    std::string AxisLegendManager::getMissionInstrumentInfo(AMDA::Info::ParamInfoSPtr paramInfo) {
        std::string info = paramInfo->getInstrumentId();
        if (info.empty())
            return info;
        std::replace(info.begin(), info.end(), '_', ' ');
        std::transform(info.begin(), info.end(), info.begin(), ::toupper);
        return info;
    }

    std::string AxisLegendManager::getMissionInfo(AMDA::Info::ParamInfoSPtr paramInfo) {
        std::string info = paramInfo->getInstrumentId();
        if (info.empty())
            return info;
        if (info.find('_') != std::string::npos) {
            info = info.substr(0, info.find('_'));
        }
        std::transform(info.begin(), info.end(), info.begin(), ::toupper);
        return info;
    }

    std::string AxisLegendManager::getTransformedUnits(const std::string& rawUnit) {
        std::string unit = rawUnit;
        if (unit.find("^"))
            unit = transformPower(unit, "^");
        if (unit.find("**"))
            unit = transformPower(unit, "**");
        return unit;
    }

    std::string AxisLegendManager::transformPower(std::string& unit, std::string pattern) {
        std::map<size_t, size_t> positions; // holds all the positions that pattern occurs within unit
        size_t pos_first = unit.find(pattern, 0);
        size_t pos_last;
        while (pos_first != std::string::npos) {
            char afterPattern = unit[pos_first + pattern.length()];
            pos_last = pos_first + pattern.length();
            if(afterPattern == '+' || afterPattern =='-'){
                afterPattern = unit[pos_first + pattern.length()+1];
                pos_last ++;
            }
            if (afterPattern == '('){
                pos_last = unit.find(")", pos_first + pattern.length())+1;
            }else if(afterPattern == '|'){
                pos_last = unit.find("|", pos_first + pattern.length()+2)+1;
            }
            else if( isdigit(afterPattern) || afterPattern == '.'){
                pos_last ++;
                while(isdigit(unit[pos_last]) || unit[pos_last] == '.'){
                    pos_last ++;
                }
            }else{
                pos_last ++;
            }
            positions.insert(std::pair<size_t, size_t>(pos_first, pos_last));
            pos_first = unit.find(pattern, pos_first + pattern.length());
        }
        std::map<size_t, size_t>::reverse_iterator it;
        for (it = positions.rbegin(); it != positions.rend(); ++it) {
            unit.insert(it->second, "#d");
            unit.erase(it->first, pattern.length());
            unit.insert(it->first, "#u");
        }
        return unit;
    }


} /* namespace plot */