//-----------------------------------------------------------------------------
//  Copyright (C) 2020 Thomas S. Ullrich
//
//  This file is part of "xyscan".
//
//  This file may be used under the terms of the GNU General Public License.
//  This project is free software; you can redistribute it and/or modify it
//  under the terms of the GNU General Public License.
//
//  Author: Thomas S. Ullrich
//  Last update: March 9, 2020
//-----------------------------------------------------------------------------
#include <QStatusBar>
#include <iostream>
#include "xyscanScanTasksHandler.h"
#include "xyscanWindow.h"
#include "xyscanErrorBarScanModeToolBox.h"
#include "xyscanDataTable.h"

#define PR(x) cout << #x << " = " << (x) << endl;

using namespace std;

xyscanScanTasksHandler::xyscanScanTasksHandler (xyscanWindow* win)
{
    mWindow = win;   // friend

    //
    //   A new handler is created everytime a new scan is started
    //   in the xyscanWindow. Here we decide what steps have to be
    //   taken to complete the whole scan.
    //
    mNx = mNxCounter = mWindow->mErrorBarScanModeToolBox->numberOfXErrorScans();
    mNy = mNyCounter = mWindow->mErrorBarScanModeToolBox->numberOfYErrorScans();

    mTaskList.clear();
    mTaskList.push_back(&xyscanScanTasksHandler::recordedXYPoint); // always

    for (int i=0; i<mNx; i++) {
        mTaskList.push_back(&xyscanScanTasksHandler::recordedLowerXError);
        mTaskList.push_back(&xyscanScanTasksHandler::recordedUpperXError);
    }

    for (int i=0; i<mNy; i++) {
        mTaskList.push_back(&xyscanScanTasksHandler::recordedLowerYError);
        mTaskList.push_back(&xyscanScanTasksHandler::recordedUpperYError);
    }

    mTaskIndex = 0;
}

void xyscanScanTasksHandler::cleanUpXErrorScan()
{
    mWindow->mPointMarker->hide();
    mWindow->mCrosshairH->show();
    mWindow->mCrosshairH->setPos(mCursorBaseHPosition);
    mWindow->mCrosshairV->setPos(mCursorBaseVPosition);
}

void xyscanScanTasksHandler::cleanUpYErrorScan()
{
    mWindow->mPointMarker->hide();
    mWindow->mCrosshairV->show();
    mWindow->mCrosshairH->setPos(mCursorBaseHPosition);
    mWindow->mCrosshairV->setPos(mCursorBaseVPosition);
}

void xyscanScanTasksHandler::disableButtons()
{
    mWindow->mSetLowerXButton->setDisabled(true);
    mWindow->mSetUpperXButton->setDisabled(true);
    mWindow->mSetLowerYButton->setDisabled(true);
    mWindow->mSetUpperYButton->setDisabled(true);
    mWindow->mErrorBarScanModeToolBox->disable();
    mWindow->mLogXRadioButton->setDisabled(true);
    mWindow->mLogYRadioButton->setDisabled(true);
}

void xyscanScanTasksHandler::enableButtons()
{
    mWindow->mSetLowerXButton->setDisabled(false);
    mWindow->mSetUpperXButton->setDisabled(false);
    mWindow->mSetLowerYButton->setDisabled(false);
    mWindow->mSetUpperYButton->setDisabled(false);
    mWindow->mErrorBarScanModeToolBox->enable();
    mWindow->mLogXRadioButton->setDisabled(false);
    mWindow->mLogYRadioButton->setDisabled(false);
}

void xyscanScanTasksHandler::finish()
{
    //
    //   Store data
    //
    double x = mPoints[0].x();
    double y = mPoints[0].y();

    vector<pair<double, double>> xerrors;
    vector<pair<double, double>> yerrors;

    int index = 1;
    for (int i=0; i<mNx; i++) {
        double lower_x = mPoints[index++].x();
        double upper_x = mPoints[index++].x();
        if (lower_x > upper_x) swap(lower_x, upper_x);  //  user mixed up lower-upper - fix it
        double lower_dx = fabs(lower_x-x);
        double upper_dx = fabs(upper_x-x);
        if (mWindow->mErrorBarScanModeToolBox->scanModeX(i) == xyscanErrorScanMode::mMax)
            lower_dx = upper_dx = max(lower_dx, upper_dx);
        else if (mWindow->mErrorBarScanModeToolBox->scanModeX(i) == xyscanErrorScanMode::mAverage)
            lower_dx = upper_dx = (lower_dx + upper_dx)/2;
        xerrors.emplace_back(lower_dx, upper_dx);;
    }

    for (int i=0; i<mNy; i++) {
        double lower_y = mPoints[index++].y();
        double upper_y = mPoints[index++].y();
        if (lower_y > upper_y) swap(lower_y, upper_y);  //  user mixed up lower-upper - fix it
        double lower_dy = fabs(lower_y-y);
        double upper_dy = fabs(upper_y-y);
        if (mWindow->mErrorBarScanModeToolBox->scanModeY(i) == xyscanErrorScanMode::mMax)
            lower_dy = upper_dy = max(lower_dy, upper_dy);
        else if (mWindow->mErrorBarScanModeToolBox->scanModeY(i) == xyscanErrorScanMode::mAverage)
            lower_dy = upper_dy = (lower_dy + upper_dy)/2;
        yerrors.emplace_back(lower_dy, upper_dy);
    }

    mWindow->mDataTable->addPoint(x, y, xerrors, yerrors, mWindow->mSignificantDigits);

    int n = mWindow->mDataTable->rowCount();
    mWindow->mMessageLabel->setText(tr("Ready to scan. Press space bar to record current cursor position."));
    if (mNx || mNy)
        mWindow->statusBar()->showMessage(tr("Data point and errors stored (%1).").arg(n), 2000);  // show for 2 sec
    else
        mWindow->statusBar()->showMessage(tr("Data point stored (%1).").arg(n), 2000);  // show for 2 sec

    enableButtons();
}

bool xyscanScanTasksHandler::performTask() // invoked every time a scan is made
{
    QPointF xy = mWindow->scan();
    mPoints.push_back(xy); // record point, sort it out later

    (this->*mTaskList[mTaskIndex])();
    mTaskIndex++;

    updateDisplays();

    if (mTaskIndex == static_cast<int>(mTaskList.size())) {
        finish();
        return false;
    }

    return true;
}

void xyscanScanTasksHandler::prepareXErrorScan()
{
    mWindow->mCrosshairH->hide();

    //
    //  Set a marker (cross) where the original point was.
    //  This helps remembering the point in case there are
    //  many close by points. Removed once the errors are
    //  all scanned.
    //
    QPointF pos(mCursorBaseVPosition.x(), mCursorBaseHPosition.y());
    pos -= QPointF(mWindow->mPointMarker->pixmap().width()/2+mWindow->mPointMarkerOffset,
                   mWindow->mPointMarker->pixmap().height()/2+mWindow->mPointMarkerOffset);
    mWindow->mPointMarker->setPos(pos);
    mWindow->mPointMarker->show();

    mWindow->mMessageLabel->setText(tr("Scan x-error (-dx): move crosshair to end of left error bar and press [space]."));
}

void xyscanScanTasksHandler::prepareYErrorScan()
{
    mWindow->mCrosshairV->hide();
    QPointF pos(mCursorBaseVPosition.x(), mCursorBaseHPosition.y());
    pos -= QPointF(mWindow->mPointMarker->pixmap().width()/2+mWindow->mPointMarkerOffset,
                   mWindow->mPointMarker->pixmap().height()/2+mWindow->mPointMarkerOffset);
    mWindow->mPointMarker->setPos(pos);
    mWindow->mPointMarker->show();

    mWindow->mMessageLabel->setText(tr("Scan y-error (-dy): "
                                       "move crosshair to end of lower "
                                       "error bar and press [space]."));
}

void xyscanScanTasksHandler::recordedLowerXError()
{
    mWindow->mMessageLabel->setText(tr("Scan x-error (+dx): move crosshair to end of right error bar and press [space]."));
}

void xyscanScanTasksHandler::recordedUpperXError()
{
    mNxCounter--;
    if (!mNxCounter) {
        cleanUpXErrorScan();
        if (mNy)
            prepareYErrorScan();
    }
    else
        mWindow->mMessageLabel->setText(tr("Scan x-error (-dx): move crosshair to end of left error bar and press [space]."));
}

void xyscanScanTasksHandler::recordedLowerYError()
{
    mWindow->mMessageLabel->setText(tr("Scan y-error (+dy): move crosshair to end of upper error bar and press [space]."));
}

void xyscanScanTasksHandler::recordedUpperYError()
{
    mNyCounter--;
    if (!mNyCounter) {
        cleanUpYErrorScan();
    }
    else
        mWindow->mMessageLabel->setText(tr("Scan y-error (-dy): move crosshair to end of lower error bar and press [space]."));
}

void xyscanScanTasksHandler::recordedXYPoint()
{
    disableButtons();  // cannot change conditions once scan has started

    mCursorBaseHPosition = mWindow->mCrosshairH->pos();  // later we return to this point after errors are scanned
    mCursorBaseVPosition = mWindow->mCrosshairV->pos();

    if (mNx)
        prepareXErrorScan();
    else if (mNy)
        prepareYErrorScan();
}

void xyscanScanTasksHandler::updateDisplays()
{
    mWindow->updatePixelDisplay();
    mWindow->updatePlotCoordinateDisplay();
    mWindow->updateStatusBarCoordinateDisplay();
    mWindow->updateZoomDisplay();
}

