// -*- c-basic-offset: 4 -*-

/** @file CPListFrame.cpp
 *
 *  @brief implementation of CPListFrame Class
 *
 *  @author Pablo d'Angelo <pablo.dangelo@web.de>
 *
 *  $Id$
 *
 *  This program is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU General Public
 *  License as published by the Free Software Foundation; either
 *  version 2 of the License, or (at your option) any later version.
 *
 *  This software is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 *  General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public
 *  License along with this software. If not, see
 *  <http://www.gnu.org/licenses/>.
 *
 */

#include "hugin_config.h"
#include "panoinc_WX.h"
#include "panoinc.h"

#include <algorithm>
#include <utility>
#include <functional>

#include "base_wx/wxPlatform.h"
#include "hugin/CPListFrame.h"
#include "hugin/MainFrame.h"
#include "base_wx/CommandHistory.h"
#include "base_wx/PanoCommand.h"
#include "base_wx/wxutils.h"
#include "hugin/huginApp.h"
#include "hugin/config_defaults.h"
#include "hugin_base/panotools/PanoToolsUtils.h"
#include "algorithms/basic/CalculateCPStatistics.h"

std::string makePairId(unsigned int id1, unsigned int id2)
{
    // Control points from same image pair, regardless of which is left or right
    // are counted the same so return the identical hash id.
    std::ostringstream oss;

    if (id1 < id2) {
        oss << id1 << "_" << id2;
    }
    else if (id2 < id1)  {
        oss << id2 << "_" << id1;
    }
    else {
        // Control points are from same image.
        oss << id1;
    }
    return oss.str();
}

CPListCtrl::CPListCtrl() : m_pano(NULL)
{
    m_sortCol = 0;
    m_sortAscend = true;
};

CPListCtrl::~CPListCtrl()
{
    wxConfigBase* config = wxConfig::Get();
    config->Write("/CPListFrame/SortColumn", m_sortCol);
    config->Write("/CPListFrame/SortAscending", m_sortAscend ? 1 : 0);
    config->Flush();
    if (m_pano)
    {
        m_pano->removeObserver(this);
    };
};

bool CPListCtrl::Create(wxWindow *parent, wxWindowID id, const wxPoint& pos,
    const wxSize& size, long style, const wxValidator& validator, const wxString& name)
{
    if (!wxListCtrl::Create(parent, id, pos, size, style))
    {
        return false;
    };
    InsertColumn(0, _("G CP#"), wxLIST_FORMAT_RIGHT, 25);
    InsertColumn(1, _("Left Img."), wxLIST_FORMAT_RIGHT, 65);
    InsertColumn(2, _("Right Img."), wxLIST_FORMAT_RIGHT, 65);
    InsertColumn(3, _("P CP#"), wxLIST_FORMAT_RIGHT, 25);
    InsertColumn(4, _("Alignment"), wxLIST_FORMAT_LEFT, 80);
    InsertColumn(5, MainFrame::Get()->IsShowingCorrelation() ? _("Correlation") : _("Distance"), wxLIST_FORMAT_RIGHT, 80);

    //get saved width
    for (int j = 0; j < GetColumnCount(); j++)
    {
        // -1 is auto
        int width = wxConfigBase::Get()->Read(wxString::Format("/CPListFrame/ColumnWidth%d", j), -1);
        if (width != -1)
        {
            SetColumnWidth(j, width);
        };
    };
    EnableAlternateRowColours(true);

    wxConfigBase* config = wxConfig::Get();
    m_sortCol=config->Read("/CPListFrame/SortColumn", 0l);
    m_sortAscend = config->Read("/CPListFrame/SortAscending", 1l) == 1;
    config->Flush();
    ShowSortIndicator(m_sortCol, m_sortAscend);
    // bind event handler
    Bind(wxEVT_CHAR, &CPListCtrl::OnChar, this);
    Bind(wxEVT_LIST_ITEM_SELECTED, &CPListCtrl::OnCPListSelectionChanged, this);
    Bind(wxEVT_LIST_ITEM_DESELECTED, &CPListCtrl::OnCPListSelectionChanged, this);
    Bind(wxEVT_LIST_COL_CLICK, &CPListCtrl::OnCPListHeaderClick, this);
    Bind(wxEVT_LIST_COL_END_DRAG, &CPListCtrl::OnColumnWidthChange, this);
    return true;
};

void CPListCtrl::Init(HuginBase::Panorama* pano)
{
    m_pano = pano;
    m_pano->addObserver(this);
    panoramaChanged(*pano);
};

wxString CPListCtrl::OnGetItemText(long item, long column) const
{
    if (item > m_internalCPList.size())
    {
        return wxEmptyString;
    };
    const HuginBase::ControlPoint& cp = m_pano->getCtrlPoint(m_internalCPList[item].globalIndex);
    switch (column)
    {
        case 0:
            return wxString::Format("%lu", static_cast<unsigned long>(m_internalCPList[item].globalIndex));
            break;
        case 1:
            return wxString::Format("%u", cp.image1Nr);
            break;
        case 2:
            return wxString::Format("%u", cp.image2Nr);
            break;
        case 3:
            return wxString::Format("%lu", static_cast<unsigned long>(m_internalCPList[item].localNumber));
            break;
        case 4:
            switch (cp.mode)
            {
                case HuginBase::ControlPoint::X_Y:
                    return wxString(_("normal"));
                    break;
                case HuginBase::ControlPoint::X:
                    return wxString(_("vert. Line"));
                    break;
                case HuginBase::ControlPoint::Y:
                    return wxString(_("horiz. Line"));
                    break;
                default:
                    return wxString::Format(_("Line %d"), cp.mode);
                    break;
            };
            break;
        case 5:
            return wxString::Format("%.2f", cp.error);
            break;
        default:
            return wxEmptyString;
    };
    return wxEmptyString;
};

int CPListCtrl::OnGetItemImage(long item) const
{
    return -1;
};

void CPListCtrl::panoramaChanged(HuginBase::Panorama &pano)
{
    m_onlyActiveImages = MainFrame::Get()->GetOptimizeOnlyActiveImages();
    const bool isShowingCorrelation = MainFrame::Get()->IsShowingCorrelation();
    wxListItem item;
    if (GetColumn(5, item))
    {
        if (isShowingCorrelation)
        {
            item.SetText(_("Correlation"));
        }
        else
        {
            item.SetText(_("Distance"));
        }
        SetColumn(5, item);
    };
    XRCCTRL(*GetParent(), "cp_list_select", wxButton)->SetLabel(isShowingCorrelation ? _("Select by Correlation") : _("Select by Distance"));
    UpdateInternalCPList();
    SetItemCount(m_internalCPList.size());
    Refresh();
};

void CPListCtrl::UpdateInternalCPList()
{
    const HuginBase::CPVector& cps = m_pano->getCtrlPoints();
    const HuginBase::UIntSet activeImgs = m_pano->getActiveImages();
    // Rebuild the global->local CP map on each update as CPs might have been
    // removed.
    m_localIds.clear();
    m_internalCPList.clear();
    m_internalCPList.reserve(cps.size());
    for (size_t i = 0; i < cps.size(); i++)
    {
        const HuginBase::ControlPoint& cp = cps[i];
        if (m_onlyActiveImages && (!set_contains(activeImgs, cp.image1Nr) || !set_contains(activeImgs, cp.image2Nr)))
        {
            continue;
        };
        CPListItem cpListItem;
        cpListItem.globalIndex = i;
        std::string pairId = makePairId(cp.image1Nr, cp.image2Nr);
        std::map<std::string, int>::iterator it = m_localIds.find(pairId);
        if (it != m_localIds.end())
        {
            ++(it->second);
        }
        else
        {
            m_localIds[pairId] = 0;
        }
        cpListItem.localNumber=m_localIds[pairId];
        m_internalCPList.push_back(cpListItem);
    };
    SortInternalList(true);
};

// sort helper function
// sort by global or local number only
#define CompareStruct(VAR, TYPESUFFIX, OP) \
struct Compare##TYPESUFFIX\
{\
    bool operator()(const CPListItem& item1, const CPListItem& item2)\
    {\
        return item1.VAR OP item2.VAR;\
    };\
};
CompareStruct(globalIndex, globalIndex, <)
CompareStruct(globalIndex, globalIndexGreater, >)
CompareStruct(localNumber, localNumber, <)
CompareStruct(localNumber, localNumberGreater, >)
#undef CompareStruct

// sort by image number, take second image number as second criterion and local number as third
#define CompareStruct(VAR1, VAR2, TYPESUFFIX, OP)\
struct Compare##TYPESUFFIX\
{\
    explicit Compare##TYPESUFFIX(const HuginBase::CPVector& cps) : m_cps(cps) {};\
    bool operator()(const CPListItem& item1, const CPListItem& item2)\
    {\
         return m_cps[item1.globalIndex].VAR1 * 1e4 + m_cps[item1.globalIndex].VAR2 + item1.localNumber * 1.0 / m_cps.size() OP\
                m_cps[item2.globalIndex].VAR1 * 1e4 + m_cps[item2.globalIndex].VAR2 + item2.localNumber * 1.0 / m_cps.size();\
    }\
private:\
    const HuginBase::CPVector& m_cps;\
};
CompareStruct(image1Nr, image2Nr, image1Nr, <)
CompareStruct(image1Nr, image2Nr, image1NrGreater, >)
CompareStruct(image2Nr, image1Nr, image2Nr, <)
CompareStruct(image2Nr, image1Nr, image2NrGreater, >)
#undef CompareStruct

// sort by mode or error
#define CompareStruct(VAR, TYPESUFFIX, OP)\
struct Compare##TYPESUFFIX\
{\
    explicit Compare##TYPESUFFIX(const HuginBase::CPVector& cps) : m_cps(cps) {};\
    bool operator()(const CPListItem& item1, const CPListItem& item2)\
    {\
         return m_cps[item1.globalIndex].VAR OP m_cps[item2.globalIndex].VAR;\
    }\
private:\
    const HuginBase::CPVector& m_cps;\
};
CompareStruct(mode, mode, <)
CompareStruct(mode, modeGreater, >)
CompareStruct(error, error, <)
CompareStruct(error, errorGreater, >)
#undef CompareStruct

void CPListCtrl::SortInternalList(bool isAscending)
{
    // nothing to sort
    if (m_internalCPList.empty())
    {
        return;
    };

    switch (m_sortCol)
    {
        case 0:
            if (m_sortAscend)
            {
                if (!isAscending)
                {
                    std::sort(m_internalCPList.begin(), m_internalCPList.end(), CompareglobalIndex());
                };
            }
            else
            {
                std::sort(m_internalCPList.begin(), m_internalCPList.end(), CompareglobalIndexGreater());
            };
            break;
        case 1:
            if (m_sortAscend)
            {
                std::sort(m_internalCPList.begin(), m_internalCPList.end(), Compareimage1Nr(m_pano->getCtrlPoints()));
            }
            else
            {
                std::sort(m_internalCPList.begin(), m_internalCPList.end(), Compareimage1NrGreater(m_pano->getCtrlPoints()));
            };
            break;
        case 2:
            if (m_sortAscend)
            {
                std::sort(m_internalCPList.begin(), m_internalCPList.end(), Compareimage2Nr(m_pano->getCtrlPoints()));
            }
            else
            {
                std::sort(m_internalCPList.begin(), m_internalCPList.end(), Compareimage2NrGreater(m_pano->getCtrlPoints()));
            };
            break;
        case 3:
            if (m_sortAscend)
            {
                std::sort(m_internalCPList.begin(), m_internalCPList.end(), ComparelocalNumber());
            }
            else
            {
                std::sort(m_internalCPList.begin(), m_internalCPList.end(), ComparelocalNumberGreater());
            };
            break;
        case 4:
            if (m_sortAscend)
            {
                std::sort(m_internalCPList.begin(), m_internalCPList.end(), Comparemode(m_pano->getCtrlPoints()));
            }
            else
            {
                std::sort(m_internalCPList.begin(), m_internalCPList.end(), ComparemodeGreater(m_pano->getCtrlPoints()));
            };
            break;
        case 5:
            if (m_sortAscend)
            {
                std::sort(m_internalCPList.begin(), m_internalCPList.end(), Compareerror(m_pano->getCtrlPoints()));
            }
            else
            {
                std::sort(m_internalCPList.begin(), m_internalCPList.end(), CompareerrorGreater(m_pano->getCtrlPoints()));
            };
            break;
    };
};

void CPListCtrl::OnCPListSelectionChanged(wxListEvent & e)
{
    if (GetSelectedItemCount() == 1)
    {
        if (e.GetIndex() < m_internalCPList.size())
        {
            MainFrame::Get()->ShowCtrlPoint(m_internalCPList[e.GetIndex()].globalIndex);
        };
    };
};

void CPListCtrl::OnCPListHeaderClick(wxListEvent& e)
{
    const int newCol = e.GetColumn();
    if (m_sortCol == newCol)
    {
        m_sortAscend = !m_sortAscend;
    }
    else
    {
        m_sortCol = newCol;
        m_sortAscend = true;
    };
    ShowSortIndicator(m_sortCol, m_sortAscend);
    SortInternalList(false);
    Refresh();
};

void CPListCtrl::OnColumnWidthChange(wxListEvent& e)
{
    const int colNum = e.GetColumn();
    wxConfigBase::Get()->Write(wxString::Format("/CPListFrame/ColumnWidth%d", colNum), GetColumnWidth(colNum));
};

void CPListCtrl::DeleteSelected()
{
    // no selected item.
    const int nSelected = GetSelectedItemCount();
    if (nSelected == 0)
    {
        wxBell();
        return;
    };

    HuginBase::UIntSet selected;
    long item = GetFirstSelected();
    long newSelection = -1;
    if (m_internalCPList.size() - nSelected > 0)
    {
        newSelection = item;
        if (item >= m_internalCPList.size() - nSelected)
        {
            newSelection = m_internalCPList.size() - nSelected - 1;
        };
    };
    while (item>=0)
    {
        // deselect item
        Select(item, false);
        selected.insert(m_internalCPList[item].globalIndex);
        item = GetNextSelected(item);
    }
    DEBUG_DEBUG("about to delete " << selected.size() << " points");
    PanoCommand::GlobalCmdHist::getInstance().addCommand(new PanoCommand::RemoveCtrlPointsCmd(*m_pano, selected));

    if (newSelection >= 0)
    {
        MainFrame::Get()->ShowCtrlPoint(m_internalCPList[newSelection].globalIndex);
        Select(newSelection, true);
    };
};

void CPListCtrl::SelectDistanceThreshold(double threshold)
{
    const bool invert = threshold < 0;
    if (invert)
    {
        threshold = -threshold;
    };
    const HuginBase::CPVector& cps = m_pano->getCtrlPoints();
    Freeze();
    for (size_t i = 0; i < m_internalCPList.size(); i++)
    {
        const double error = cps[m_internalCPList[i].globalIndex].error;
        Select(i, ((error > threshold) && (!invert)) || ((error < threshold) && (invert)));
    };
    Thaw();
};

void CPListCtrl::SelectAll()
{
    for (long i = 0; i < m_internalCPList.size(); i++)
    {
        Select(i, true);
    };
};

void CPListCtrl::OnChar(wxKeyEvent& e)
{
    switch (e.GetKeyCode())
    {
        case WXK_DELETE:
        case WXK_NUMPAD_DELETE:
            DeleteSelected();
            break;
        case WXK_CONTROL_A:
            SelectAll();
            break;
        default:
            e.Skip();
    };
};


IMPLEMENT_DYNAMIC_CLASS(CPListCtrl, wxListCtrl)

IMPLEMENT_DYNAMIC_CLASS(CPListCtrlXmlHandler, wxListCtrlXmlHandler)

CPListCtrlXmlHandler::CPListCtrlXmlHandler()
: wxListCtrlXmlHandler()
{
    AddWindowStyles();
}

wxObject *CPListCtrlXmlHandler::DoCreateResource()
{
    XRC_MAKE_INSTANCE(cp, CPListCtrl)
    cp->Create(m_parentAsWindow, GetID(), GetPosition(), GetSize(), GetStyle("style"), wxDefaultValidator, GetName());
    SetupWindow(cp);
    return cp;
}

bool CPListCtrlXmlHandler::CanHandle(wxXmlNode *node)
{
    return IsOfClass(node, "CPListCtrl");
}

CPListFrame::CPListFrame(wxWindow* parent, HuginBase::Panorama& pano) : m_pano(pano)
{
    DEBUG_TRACE("");
    bool ok = wxXmlResource::Get()->LoadDialog(this, parent, "cp_list_frame");
    DEBUG_ASSERT(ok);
    m_list = XRCCTRL(*this, "cp_list_frame_list", CPListCtrl);
    DEBUG_ASSERT(m_list);
    m_list->Init(&m_pano);

    //set minumum size
    SetSizeHints(200, 300);
    //size
    hugin_utils::RestoreFramePosition(this, "CPListFrame");
    Bind(wxEVT_CLOSE_WINDOW, &CPListFrame::OnClose, this);
    Bind(wxEVT_BUTTON, &CPListFrame::OnDeleteButton, this, XRCID("cp_list_delete"));
    Bind(wxEVT_BUTTON, &CPListFrame::OnSelectButton, this, XRCID("cp_list_select"));
}

CPListFrame::~CPListFrame()
{
    DEBUG_TRACE("dtor");
    hugin_utils::StoreFramePosition(this, "CPListFrame");
    DEBUG_TRACE("dtor end");
}

void CPListFrame::OnClose(wxCloseEvent& event)
{
    DEBUG_DEBUG("OnClose");
    MainFrame::Get()->OnCPListFrameClosed();
    DEBUG_DEBUG("closing");
    Destroy();
}

void CPListFrame::OnDeleteButton(wxCommandEvent & e)
{
    m_list->DeleteSelected();
}

void CPListFrame::OnSelectButton(wxCommandEvent & e)
{
    double threshold;
    const bool isShowingCorrelation = MainFrame::Get()->IsShowingCorrelation();
    if(isShowingCorrelation)
    { 
        threshold = HUGIN_FT_CORR_THRESHOLD;
        wxConfig::Get()->Read("/Finetune/CorrThreshold", &threshold, HUGIN_FT_CORR_THRESHOLD);;
    }
    else
    {
        // calculate the mean error and the standard deviation
        HuginBase::PTools::calcCtrlPointErrors(m_pano);
        double min, max, mean, var;
        HuginBase::CalculateCPStatisticsError::calcCtrlPntsErrorStats(m_pano, min, max, mean, var);

        // select points whos distance is greater than the mean
        // hmm, maybe some theory would be nice.. this is just a
        // guess.
        threshold = mean + sqrt(var);
    };
    wxString t;
    do
    {
        t = wxGetTextFromUser(isShowingCorrelation ? 
            _("Enter minimum control point correlation.\nAll points with lower correlation will be selected.") : 
            _("Enter minimum control point error.\nAll points with a higher error will be selected"), 
            _("Select Control Points"),
            hugin_utils::doubleTowxString(threshold, 2));
        if (t == wxEmptyString) {
            // do not select anything
            return;
        }
    }
    while (!hugin_utils::str2double(t, threshold));

    m_list->SelectDistanceThreshold(isShowingCorrelation ? -threshold : threshold);
};
