#include "mainwindow.h"
#include "annotationxml.h"
#include "ui_mainwindow.h"
#include <QImage>
#include <QDebug>
#include <QLabel>
#include <QWidget>
#include <QPainter>
#include <QFileDialog>
#include <QToolButton>
#include <QGridLayout>
#include <QMessageBox>
#include <QStatusBar>
#include <QTreeView>
#include <QMenu>
#include <QCursor>
#include <QInputDialog>
#include <QDateTime>
#include <CSVReader.h>
#include "changemask.h"
#include "changepacket.h"
#include "tinyxml2.h"
#include "classoptions.h"
#include "requestfromserver.h"
#include "setids.h"

// Hilfstexte
QString toolTitel[] = { "Objekte Verstecken", "Verschieben", "Neues Objekt", "Kopieren", "Einfügen", "Löschen", "Zerschneiden", "Vergrößern", "Allgemeine Information" };
QString toolBeschreibung[] {"Klicken sie mit der Linken Maustaste auf ein Objekt um es zu verstecken, oder wieder sichtbar zu machen.\nVersteckte Objekte werden von anderen Werkzeugen ignoriert.",
                            "Halten sie die linke Maustaste gedrückt, um die Ecke eines Objektes zu verschieben, wenn sich die Maus in der Nähe befindet.\nBefindet sich keine Ecke in der Nähe der Maus, so wird eine neue Ecke erzeugt.",
                            "Klicken sie mit der Linken Maustaste an die Stellen, wo die Ecken des neuen Objektes sind.\nUm den Vorgang abzuschließen klicken sie erneut auf die erste Ecke.\nMit Rechtsklick kann die vorherige Ecke wieder entfernt werden.",
                            "Klicken sie mit der linken Maustaste auf das Objekt, welches sie kopieren möchten.",
                            "Klicken sie mit der linken Maustaste auf ein Objekt, um die ID und die Klasse des kopierten Objektes auf dieses zu übertragen.\nKlicken sie an eine freie Stelle um eine Kopie des kopierten Objektes einzufügen.\nHalten sie die rechte Maustaste gedrückt, um das kopierte Objekt zu drehen.",
                            "Halten sie die linke Maustaste gedrückt, um einen Bereich festzulegen, in dem alle Eckpunkte gelöscht werden sollen.",
                            "Klicken sie auf zwei Eckpunkte eines Objektes, um es in zwei verschiedene Objekte zu zerschneiden.",
                            "Halte die linke Maustaste gedrückt um ein Gebiet zu makieren, an welches herangezoomt werden soll.",
                            "Klicken sie auf Öffnen und wählen sie einen Ordner aus, um eine annotierte Sequenz zu laden oder eine neue Sequenz zu erstellen."};

// Inhalt der MainWindow Klasse
//------------------------------

MainWindow::MainWindow( QApplication *app, QWidget *parent ) :
    QMainWindow(parent),
    app( app ),
    ui(new Ui::MainWindow),
    contextMenu( 0 ),
    seq( 0 ),
    m( 0 ),
    serverAddress( "tcp://ob:55556" )
{
    ui->setupUi(this);
    setWindowTitle( "Annotation GUI v. 1.0" );
    status = new QLabel( "fertig" );
    ui->statusBar->layout()->setSizeConstraint(QLayout::SetMinimumSize);
    status->setSizePolicy( QSizePolicy::Minimum, QSizePolicy::Minimum );
    ui->statusBar->layout()->addWidget( status );
    ui->statusBar->addWidget( status );

    workModel = new ArbeitsModel( this );
    ArbeitsController::initStaticController( workModel );
    v = new ArbeitsView( workModel, ui->viewWidget );
    v->setController( ArbeitsController::getController( MOVE ) );
    workModel->addView( v );
    ui->toolTitel->setText( toolTitel[ USERMODE_COUNT ] );
    ui->toolBeschreibung->setText( toolBeschreibung[ USERMODE_COUNT ] );

    ui->framesTree->setContextMenuPolicy( Qt::CustomContextMenu );
    ui->viewWidget->setContextMenuPolicy( Qt::CustomContextMenu );

    QList<QToolButton *> btnList = ui->buttons->findChildren<QToolButton*>();
    for( auto i = btnList.begin(); i != btnList.end(); i++ )
    {
        (*i)->setIconSize( (*i)->size() );
    }
}

MainWindow::~MainWindow()
{
    if( seq )
    {
        QMessageBox::StandardButton reply = QMessageBox::question(this, "Achtung", "Möchten sie die Annotationen speichern?",
                                                                  QMessageBox::Yes|QMessageBox::No);
        if (reply == QMessageBox::Yes) {
            seq->saveToPath( status );
        }
        seq->refRelease();
    }
    delete status;
    delete ui;
    delete m;
    delete contextMenu;
}

// Erstellt den Navigationsbaum
void MainWindow::setupFrameTree()
{
    ui->framesTree->setUpdatesEnabled(false);
    if( !seq )
        return;
    if( !m )
    {
        m = new FrameTreeModel();
        seq->refNew();
        m->setSequenz( seq );
        ui->framesTree->setModel( m );
        ui->framesTree->setSelectionMode( ui->framesTree->MultiSelection );
    }
    ui->framesTree->collapseAll();
    setFrameTreeSelection();
    repaintFrameTree();
    ui->framesTree->setUpdatesEnabled(true);
}

// Aktualisiert die Ausgefählten Objekte im Navigationsbaum
void MainWindow::setFrameTreeSelection()
{
    ui->framesTree->expand( m->getCameraSelectionIndex() );
    ui->framesTree->expand( m->getFrameSelectionIndex() );
    ui->framesTree->selectionModel()->select( m->getCameraSelectionIndex(), QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows );
    ui->framesTree->selectionModel()->select( m->getFrameSelectionIndex(), QItemSelectionModel::Select | QItemSelectionModel::Rows );
    QList<QModelIndex> indices = m->getSelectedPackages();
    for( auto index = indices.begin(); index != indices.end(); index++ )
        ui->framesTree->selectionModel()->select( *index, QItemSelectionModel::Select | QItemSelectionModel::Rows );
    ui->framesTree->scrollTo(m->getFrameSelectionIndex());
    ui->framesTree->repaint();
}

// Zeichnet den Navigazionsbaum neu
void MainWindow::repaintFrameTree()
{
    m->update();
    ui->framesTree->repaint();
}

// Setzt den Controller der Arbeitsfläche
void MainWindow::setMode( UserMode m )
{
    if( workModel->getMode() == m )
        ArbeitsController::getController( m )->restartController();
    workModel->setMode( m );
    v->setController( ArbeitsController::getController( m ) );
    ui->toolTitel->setText( toolTitel[ workModel->getMode() ] );
    ui->toolBeschreibung->setText( toolBeschreibung[ workModel->getMode() ] );
}

// Gibt die geladene Sequenz zurück
Sequenz *MainWindow::getSequenz()
{
    return seq;
}

// Wählt alle Buttons für die Werkzeuge ab
void MainWindow::unselectButttons()
{
    QList<QToolButton *> btnList = ui->buttons->findChildren<QToolButton*>();
    for( auto i = btnList.begin(); i != btnList.end(); i++ )
    {
        (*i)->setChecked( false );
    }
}

// Fragt nach Vorabannotationen beim Server
void MainWindow::requestFromServer()
{
    if( serverAddress != "" )
    {
        Frame *f = seq->getFrame();
        RequestFromServer rfs( serverAddress, f );
        rfs.exec();
        f->setObjects( rfs.getObjects() );
        f->applyMask(*((Kamera*)f->getParent())->getMask());
        if( !seq->hasAnnotatedObjects() )
        {
            int id = 0;
            for( auto obj = f->getObjects().begin(); obj != f->getObjects().end(); obj++ )
            {
                (*obj)->setId( QString::number( id ) );
                seq->addObjectName( QString::number( id ) );
                id++;
            }
        }
    }
    else
        seq->getFrame()->setObjects( std::vector< std::vector< cv::Point > >());
    setupFrameTree();
    v->repaint();
}

// Öffnet eine Sequenz
void MainWindow::on_actionOpen_triggered()
{
    QString path = QFileDialog::getExistingDirectory(this,
                                                     tr("Open Image Directory"),
                                                     "/studi-tmp/kstrohm",
                                                     QFileDialog::ShowDirsOnly |
                                                     QFileDialog::DontResolveSymlinks);
    if( path == "" )
        return;
    status->setText( "Lade Annotation ..." );
    AnnotationLoader loader( path, status );
    if( seq )
        seq->refRelease();
    seq = loader.getSequenz();
    if( !seq )
    {
        QMessageBox::StandardButton reply = QMessageBox::question(this, "Achtung", "In dem ausgewählten Ordner wurde keine Annotierte Sequenz gefunden. Möchten sie eine neue Sequenz erstellen?",
                                                                  QMessageBox::Yes|QMessageBox::No);
        if (reply == QMessageBox::Yes) {
            AnnotationCreator creator( path, status );
            seq = creator.getSequenz();
            if( !seq )
            {
                QMessageBox::critical( this, "Fehler", "Es ist ein Fehler beim erstellen der Sequenz aufgetreten." );
            }
        }
    }
    status->setText( "fertig" );
    if( seq )
    {
        ui->framesTree->setModel( 0 );
        delete m;
        m = 0;
        Frame *f = seq->getFrame();
        workModel->setFrame( f );
        workModel->setMask( seq->getCameras().at( seq->getSelectedCamera() )->getMask()->getDrawableImage() );
        workModel->notifyViews();
        if( f->isNotAnnotated() )
            requestFromServer();
        else
            setupFrameTree();
        setMode( MOVE );
    }
}

// Wechselt zum nächsten Bild
void MainWindow::on_actionNext_triggered()
{
    ui->framesTree->selectionModel()->clear();
    int oldCam = seq->getSelectedCamera();
    QModelIndex camIndex = m->getCameraSelectionIndex();
    ui->framesTree->collapse( m->getFrameSelectionIndex() );
    seq->nextFrame();
    if(oldCam != seq->getSelectedCamera())
        ui->framesTree->collapse( camIndex );
    Frame *f = seq->getFrame();
    workModel->setFrame( f );
    workModel->setMask( seq->getCameras().at( seq->getSelectedCamera() )->getMask()->getDrawableImage() );
    workModel->notifyViews();
    if( f->isNotAnnotated() )
        requestFromServer();
    else
        setFrameTreeSelection();
}

// Wechselt zum vorherigen Bild
void MainWindow::on_actionBefore_triggered()
{
    ui->framesTree->selectionModel()->clear();
    int oldCam = seq->getSelectedCamera();
    QModelIndex camIndex = m->getCameraSelectionIndex();
    ui->framesTree->collapse( m->getFrameSelectionIndex() );
    seq->previousFrame();
    if(oldCam != seq->getSelectedCamera())
        ui->framesTree->collapse( camIndex );
    Frame *f = seq->getFrame();
    workModel->setFrame( f );
    workModel->setMask( seq->getCameras().at( seq->getSelectedCamera() )->getMask()->getDrawableImage() );
    workModel->notifyViews();
    if( f->isNotAnnotated() )
        requestFromServer();
    else
        setFrameTreeSelection();
}

// Wechselt zum nächsten Bild
void MainWindow::on_next_clicked()
{
    on_actionNext_triggered();
}

// Wechselt zum vorherigen Bild
void MainWindow::on_before_clicked()
{
   on_actionBefore_triggered();
}

// Setzt die Vergrößerung der Arbeitsfläche zurück
void MainWindow::on_zoom_out_clicked()
{
    workModel->resetZoom();
    workModel->notifyViews();
}

// Wechselt in den ZOOM_IN Modus
void MainWindow::on_zoom_in_clicked()
{
    unselectButttons();
    ui->zoom_in->setChecked( true );
    setMode( ZOOM_IN );
    v->repaint();
}

// Wechselt in den DELETE Modus
void MainWindow::on_remove_clicked()
{
    unselectButttons();
    ui->remove->setChecked( true );
    setMode( DELETE );
    v->repaint();
}

// Wechselt in den NEW Modus
void MainWindow::on_polygon_clicked()
{
    unselectButttons();
    ui->polygon->setChecked( true );
    setMode( NEW );
}

// Wechselt in den MOVE Modus
void MainWindow::on_move_clicked()
{
    unselectButttons();
    ui->move->setChecked( true );
    setMode( MOVE );
}

// Wechselt zum angeklickten Bild des Navigationsbaumes
void MainWindow::on_framesTree_clicked(const QModelIndex &index)
{
    if( ((FrameTreeNode*)index.internalPointer())->getDepth() == 2 )
        ((_ObjectPolygon*)index.internalPointer())->setSelected( ui->framesTree->selectionModel()->isSelected( index ) );
    ui->framesTree->selectionModel()->clear();
    int cam, fr;
    if( m && m->clickedOnFrame( index, cam, fr ) )
    {
        if( cam != seq->getSelectedCamera() || fr != seq->getSelectedFrame() )
        {
            if(cam != seq->getSelectedCamera())
                ui->framesTree->collapse( m->getCameraSelectionIndex() );
            ui->framesTree->collapse( m->getFrameSelectionIndex() );
            seq->selectFrame( cam, fr );
            Frame *f = seq->getFrame();
            workModel->setFrame( f );
            workModel->setMask( seq->getCameras().at( seq->getSelectedCamera() )->getMask()->getDrawableImage() );
            workModel->notifyViews();
            if( f->isNotAnnotated() )
                requestFromServer();
        }
    }
    if( ((FrameTreeNode*)index.internalPointer())->getDepth() == 0 )
    {
        if( ((FrameTreeNode*)index.internalPointer())->getIndex() != seq->getSelectedCamera() )
        {
            ui->framesTree->collapse( m->getCameraSelectionIndex() );
            ui->framesTree->collapse( m->getFrameSelectionIndex() );
            seq->selectFrame( ((FrameTreeNode*)index.internalPointer())->getIndex(), 0 );
            Frame *f = seq->getFrame();
            workModel->setFrame( f );
            workModel->setMask( seq->getCameras().at( seq->getSelectedCamera() )->getMask()->getDrawableImage() );
            workModel->notifyViews();
            if( f->isNotAnnotated() )
                requestFromServer();
        }
    }
    if( ((FrameTreeNode*)index.internalPointer())->getDepth() == 2 )
    {
        if( ((FrameTreeNode*)index.internalPointer())->getParent()->getParent()->getIndex() != seq->getSelectedCamera() )
            ui->framesTree->collapse( m->getCameraSelectionIndex() );
        if( ((FrameTreeNode*)index.internalPointer())->getParent()->getIndex() != seq->getSelectedFrame() )
            ui->framesTree->collapse( m->getFrameSelectionIndex() );
        seq->selectFrame( ((FrameTreeNode*)index.internalPointer())->getParent()->getParent()->getIndex(),
                ((FrameTreeNode*)index.internalPointer())->getParent()->getIndex() );
        Frame *f = seq->getFrame();
        workModel->setFrame( f );
        workModel->setMask( seq->getCameras().at( seq->getSelectedCamera() )->getMask()->getDrawableImage() );
        workModel->notifyViews();
        if( f->isNotAnnotated() )
            requestFromServer();
    }
    setFrameTreeSelection();
    v->repaint();
}

// Wechselt in den SELECT Modus
void MainWindow::on_select_clicked()
{
    unselectButttons();
    ui->select->setChecked( true );
    setMode( SELECT );
}

// Zeigt ein Rechtsklick Menü im Navigationsbaum an
void MainWindow::on_framesTree_customContextMenuRequested(const QPoint &pos)
{
    QModelIndex index = ui->framesTree->indexAt( pos );
    ui->framesTree->selectionModel()->select( index, QItemSelectionModel::Select | QItemSelectionModel::Rows );
    on_framesTree_clicked( index );
    QAction *trunc = 0;
    if( contextMenu )
        delete contextMenu;
    contextMenu = new QMenu( ui->framesTree );
    switch( ((FrameTreeNode*)index.internalPointer())->getDepth() )
    {
    case 0:
        contextMenu->addAction( "Maske Bearbeiten ..." );
        break;
    case 1:
        contextMenu->addAction( "Vom Server abfragen ..." );
        break;
    case 2:
        contextMenu->addAction( "Objekt ID bearbeiten ..." );
        contextMenu->addAction( "Objekt Klasse bearbeiten ..." );
        contextMenu->addAction( "Objekt löschen" );
        if( ((_ObjectPolygon*)index.internalPointer())->getPolygonList().size() > 1 )
            contextMenu->addAction( "Objekt nach Polygonen aufteilen" );
        trunc = contextMenu->addAction( "Abgeschnitten" );
        trunc->setCheckable( true );
        trunc->setText( "Abgeschnitten" );
        trunc->setChecked( ((_ObjectPolygon*)index.internalPointer())->isTruncated() );
        break;
    }
    QAction *action = contextMenu->exec( QCursor::pos() );
    if( !action )
        return;
    if( action->text() == "Maske Bearbeiten ..." )
    {
        ChangeMask chm( ((Kamera*)index.internalPointer())->getMask(), (Kamera*)index.internalPointer() );
        chm.exec();
        ((Kamera*)index.internalPointer())->getMask()->unloadMask();
        setupFrameTree();
    }
    if( action->text() == "Vom Server abfragen ..." )
        requestFromServer();
    if( action->text() == "Objekt ID bearbeiten ..." )
    {
        ChangePacket chp( ObjectPolygon( (_ObjectPolygon*)index.internalPointer() ), seq );
        chp.exec();
        workModel->notifyViews();
        setupFrameTree();
    }
    if( action->text() == "Objekt Klasse bearbeiten ..." )
    {
        QList< Sequenz::SegmentationClass > classes = seq->getClasses();
        QList< QString > classNames;
        QString packetId = ((_ObjectPolygon*)index.internalPointer())->getId();
        int currentClass = seq->getClassOfObject( packetId );
        int classId = -1;
        foreach( auto c, classes )
        {
            if( currentClass == c.id )
                classId = classNames.size();
            classNames.append( c.name );
        }
        QString className = QInputDialog::getItem( this, "Klasse Bearbeiten", "Klasse von Objekt " + packetId, classNames, classId, false );
        seq->setClassOfObject( packetId, seq->getClassId( className ) );
        setupFrameTree();
    }
    if( action->text() == "Objekt Löschen" )
    {
        Frame *f = seq->getFrame();
        f->removeObject( ObjectPolygon( (_ObjectPolygon*)index.internalPointer() ) );
        setupFrameTree();
        v->repaint();
    }
    if( action->text() == "Objekt nach Polygonen aufteilen" )
    {
        seq->getFrame()->disconnectObject( ObjectPolygon( ((_ObjectPolygon*)index.internalPointer()) ) );
        setupFrameTree();
        v->repaint();
    }
    if( action == trunc )
        ((_ObjectPolygon*)index.internalPointer())->setTruncated( !((_ObjectPolygon*)index.internalPointer())->isTruncated() );
}

// Speichert die geladene Sequenz
void MainWindow::on_actionSave_triggered()
{
    if( !seq )
        return;
    status->setText( "speichere Annotation ..." );
    seq->saveToPath( status );
    status->setText( "fertig" );
}

// Wechselt in den Modus PIPETTE_SELECT
void MainWindow::on_pipette_clicked()
{
    unselectButttons();
    ui->pipette->setChecked( true );
    setMode( PIPETTE_SELECT );
}

// Wechselt in den CUT Modus
void MainWindow::on_cut_clicked()
{
    unselectButttons();
    ui->cut->setChecked( true );
    setMode( CUT );
}

// Zeigt die Option zum einstellen der Serveradresse an
void MainWindow::on_actionServer_address_triggered()
{
    serverAddress = QInputDialog::getText( this, "Server Adresse ändern", "Server Adresse:", QLineEdit::Normal, serverAddress );
}

// Zeigt die Klassen Verwaltungsoberfläche an
void MainWindow::on_actionKlassen_verwalten_triggered()
{
    if( seq )
    {
        ClassOptions cop( seq, this );
        cop.exec();
    }
    else
        QMessageBox::critical( this, "Fehler", "Sie müssen zunächst eine Sequenz geöffnet haben, bevor sie die Klassen verwalten können." );
}

// Zeigt ein Rechtsklick Menü auf der Arbeitsfläche an
void MainWindow::on_viewWidget_customContextMenuRequested(const QPoint &pos)
{
    QPoint rPos = pos - v->pos();
    if( !seq || !seq->getFrame() )
    {
        v->mouseReleaseEvent( 0 );
        return;
    }
    QPoint tPos = workModel->inverseTranslate( rPos );
    int pIndex;
    ObjectPolygon obj = seq->getFrame()->getObjectAt( tPos, pIndex );
    if( obj.isNull() )
        return;
    v->mouseReleaseEvent( 0 );
    if( contextMenu )
        delete contextMenu;
    contextMenu = new QMenu( ui->viewWidget );
    contextMenu->addAction( "Objekt ID bearbeiten ..." );
    contextMenu->addAction( "Objekt Klasse bearbeiten ..." );
    contextMenu->addAction( "Objekt Loeschen" );
    if( obj->getPolygonList().size() > 1 )
        contextMenu->addAction( "Objekt nach Polygonen aufteilen" );
    QAction *trunc = contextMenu->addAction( "Abgeschnitten" );
    trunc->setCheckable( true );
    trunc->setText( "Abgeschnitten" );
    trunc->setChecked( obj->isTruncated() );

    QAction *action = contextMenu->exec( QCursor::pos() );
    if( !action )
        return;
    if( action->text() == "Objekt ID bearbeiten ..." )
    {
        ChangePacket chp( obj, seq );
        chp.exec();
        workModel->notifyViews();
        setupFrameTree();
    }
    if( action->text() == "Objekt Klasse bearbeiten ..." )
    {
        QList< Sequenz::SegmentationClass > classes = seq->getClasses();
        QList< QString > classNames;
        QString packetId = obj->getId();
        int currentClass = seq->getClassOfObject( packetId );
        int classId = -1;
        foreach( auto c, classes )
        {
            if( currentClass == c.id )
                classId = classNames.size();
            classNames.append( c.name );
        }
        QString className = QInputDialog::getItem( this, "Klasse Bearbeiten", "Klasse von Objekt " + packetId, classNames, classId, false );
        seq->setClassOfObject( packetId, seq->getClassId( className ) );
        setupFrameTree();
    }
    if( action->text() == "Objekt Loeschen" )
    {
        Frame *f = seq->getFrame();
        f->removeObject( obj );
        setupFrameTree();
        v->repaint();
    }
    if( action->text() == "Objekt nach Polygonen aufteilen" )
    {
        seq->getFrame()->disconnectObject( obj );
        setupFrameTree();
        v->repaint();
    }
    if( action == trunc )
        obj->setTruncated( !obj->isTruncated() );
}

// Setzt das anzeigen der Maske in der Arbeitsfläche
void MainWindow::on_actionMake_anzeigen_toggled(bool checked)
{
    workModel->setShowMask( checked );
    workModel->notifyViews();
}

// Setzt die Option des einfärbens von Objekten in der Arbeitsfläche
void MainWindow::on_actionObjekte_faerben_triggered(bool checked)
{
    workModel->setShowColors( checked );
    workModel->notifyViews();
}

// Setzt die Option des Anzeigens von IDs in der Arbeitsfläche
void MainWindow::on_actionIDs_anzeigen_triggered(bool checked)
{
    workModel->setShowId( checked );
    workModel->notifyViews();
}

// Zeigt die Maskenbearbeitungsoberfläche
void MainWindow::on_actionMaske_bearbeiten_triggered()
{
    if( !seq )
        return;
    ChangeMask chm( seq->getCameras().at( seq->getSelectedCamera() )->getMask(), seq->getCameras().at( seq->getSelectedCamera() ) );
    chm.exec();
    setupFrameTree();
}

// zeigt die intelligente Id Bearbeitungsoberfläche an
void MainWindow::on_actionIDs_bearbeiten_triggered()
{
    if( !seq )
        return;
    SetIds ids( seq, this );
    ids.exec();
    v->update();
    setupFrameTree();
}
