#include "frame.h"
#include "kamera.h"
#include <QImage>
#include <QMessageBox>
#include <QDebug>


// Erstellt das Bild
//  imgPath: Der Pfad zu dem Quellbild
//  timestamp: Der Zeitpunkt der Aufnahme des Bildes
//  index: Der Index des Bildes in der Liste aller Bilder der Kamera
//  kam: Die Kamera, die das Bild aufgenommen hat
//  needAnnotation: true, falls das Bild noch nicht annotiert wurde
Frame::Frame( QString imgPath, QString timestamp, int index, Kamera *kam, bool needAnnotation )
    : FrameTreeNode( index, kam, 1 ),
      path( imgPath ),
      timestamp( timestamp ),
      needAnnotation( needAnnotation ),
      size( QSize( 0, 0 ) ),
      needSave( 0 )
{}

Frame::~Frame()
{}

// Gibt this zurück
void *Frame::getNodeObject() const
{
    return (void*)this;
}

// Gibt die Anzahl aller Objekte auf dem Bild zurück
int Frame::getChildCount() const
{
    return objects.size();
}

// Entfernt alle Eckpunkte in einem Gebit
//  area: Das Gebiet, in dem die Eckpunkte gelöscht werden sollen
void Frame::removeSelectedVertices( QRect area )
{
    needSave = 1;
    int anz = objects.size();
    for( int i = 0; i < anz; i++ )
    {
        ObjectPolygon o = objects.at( i );
        if( o->isSelected() )
        {
            if( !o->removeVertices( area ) )
            {
                objects.removeAt( i );
                i--;
                anz--;
            }
        }
    }
}

// Löscht ein bestimmtes Objekt
//  o: Das Objekt, welches gelöscht werden soll
void Frame::removeObject( ObjectPolygon o )
{
    needSave = 1;
    objects.removeOne( o );
}

// Fügt ein Objekt zu dem Bild hinzu. Das Objekt wird automatisch am
// Bildrand abgeschnitten
//  name: Die ID des Objektes
//  truncated: true, falls das Objekt abgeschnitten ist
//  po: Eine Liste mit Polygonen, welche die Umrisse des Objektes sind
void Frame::addObject( QString id, bool truncated, QList< QPolygon > po )
{
    needSave = 1;
    if( po.size() < 1 )
        return;
    if( size != QSize( 0, 0 ) )
    {
        QPolygon cliped = clipPolygon( po.at( 0 ) );
        ObjectPolygon newObject( id, cliped, truncated || cliped.boundingRect() != po.at( 0 ).boundingRect(), objects.size(), this );
        int pAnz = po.count();
        for( int i = 1; i < pAnz; i++ )
        {
            cliped = clipPolygon( po.at( i ) );
            if( cliped.boundingRect() != po.at( i ).boundingRect() )
                newObject->setTruncated( true );
            newObject->getPolygonList().append( cliped );
        }
        objects.append( newObject );
    }
    else
    {
        ObjectPolygon newObject( id, po.at( 0 ), truncated, objects.size(), this );
        int pAnz = po.count();
        for( int i = 1; i < pAnz; i++ )
            newObject->getPolygonList().append( po.at( i ) );
        objects.append( newObject );
    }
}

// Wendet die Maske auf das Bild an und löscht alle Objekte, welche sich in
// verbotenen Bereichen befinden
//  m: die Maske
void Frame::applyMask( Mask &m )
{
    needSave = 1;
    int count = objects.count();
    for( int i = 0; i < count; i++ )
    {
        int pAnz = objects.at( i )->getPolygonList().count();
        for( int j = 0; j < pAnz; j++  )
        {
            if( !m.isPolygonInside( objects.at( i )->getPolygonList().at( j ) ) )
            {
                pAnz--;
                objects.at( i )->getPolygonList().removeAt( j-- );
                if( pAnz <= 0 )
                {
                    count--;
                    objects.removeAt( i-- );
                }
            }
        }
    }
}

// Spaltet ein Objekt in zwei Teile
//  object: Das Objekt welches zerteilt werden soll
//  beginn: Der Index des Start Eckpunktes
//  end: Der Index des End Eckpunktes
//  pIndex: Der Index des Polygons, welches zerteilt werden soll
void Frame::splitObject( ObjectPolygon object, int begin, int end, int pIndex )
{
    needSave = 1;
    QPolygon newP = object->split( begin, end, pIndex );
    QList< QPolygon > newPols;
    newPols.append( newP );
    addObject( object->getId(), object->isTruncated(), newPols );
}

// gibt den Namen des Bildes zurück
QString Frame::getName() const
{
    return path.mid( path.lastIndexOf( '/' ) + 1 );
}

// Gibt den Aufnahmezeitpunkt des Bildes zurück
QString Frame::getTimestamp() const
{
    return timestamp;
}

// Gibt eine Liste mit Objekten zurück, welche sich auf dem Bild befinden
QList< ObjectPolygon > &Frame::getObjects()
{
    return objects;
}

// Setzt die Objekte, welche sich auf dem Bild befinden. Alle vorherigen
// Objekte werden entfernt.
//  objects: die Liste mit den neuen Objekten
void Frame::setObjects( std::vector< std::vector< cv::Point > > obs )
{
    needSave = 1;
    needAnnotation = false;
    objects.clear();
    for( auto p = obs.begin(); p != obs.end(); p++ )
    {
        std::vector< cv::Point > pol;
        cv::approxPolyDP( *p, pol, 15, 1 );
        if( pol.size() > 2 )
        {
            QPolygon polygon;
            int count = 0;
            for( auto point = pol.begin(); point != pol.end(); point++, count++ )
            {
                polygon.append( QPoint( point->x, point->y ) );
            }
            QList< QPolygon > newPols;
            newPols.append( polygon );
            addObject( "-1", 0, newPols );
        }
    }
}

// Gibt das geladene Bild als Objekt des Qt Frameworks zurück
QImage Frame::getImage()
{
    QImage img( path );
    size = img.size();
    return img;
}

// Gibt das geladene Bild als Objekt der OpenCV Bibliotek zurück
cv::Mat Frame::getImageMatrix() const
{
    return cv::imread( path.toStdString() );
}

// Gibt den Bildausschnitt zurück, auf dem ein Objekt komplett sichtbar ist
//  objectId: die ID des Objektes, welches auf dem Bild sein soll
QImage Frame::getObjectImage( QString objectId )
{
    for( ObjectPolygon o : objects )
    {
        if( o->getId() == objectId )
        {
            QImage ret = getImage().copy( o->getBoundingBox() );
            return ret;
        }
    }
    return QImage();
}

// Gibt den Bildausschnitt zurück, auf dem ein Objekt komplett sichtbar ist
//  object: das Objekt, welches auf dem Bild sein soll
QImage Frame::getObjectImage( ObjectPolygon o )
{
    QImage ret = getImage().copy( o->getBoundingBox() );
    return ret;
}

// Prüft ob ein Objekt auf dem Bild vorhanden ist
//  id: Die ID des Objektes
bool Frame::hasObject( QString id )
{
    for( ObjectPolygon o : objects )
    {
        if( o->getId() == id )
            return true;
    }
    return false;
}

// Gibt true zurück, falls kein Fehler in der Annotation des Bildes gefunden
// wurde
bool Frame::isCorrectAnnotated() const
{
    bool good = true;
    for( ObjectPolygon o1 : objects )
    {
        for( ObjectPolygon o2 : objects)
        {
            if( o1 == o2 )
                continue;
            good &= o2->getId() != o1->getId();
        }
        good &= o1->getId() != "-1";
    }
    return good;
}

// Gibt true zurück, falls das Bild noch nicht annotiert wurde
bool Frame::isNotAnnotated() const
{
    return needAnnotation;
}

// Gibt das Objekt zurück, welches an einer bestimmten Position im Bild ist.
//  0 falls an der Stelle kein Objekt ist
//  pos: die Position, an der ein Objekt gesucht werden soll
//  pIndex: wird auf den Index des Polygons gesetzt, welches an der Stelle
// ist, falls dort ein Objekt existiert
ObjectPolygon Frame::getObjectAt( QPoint pos, int &pIndex ) const
{
    for( ObjectPolygon o : objects )
    {
        pIndex = 0;
        for( QPolygon pol : o->getPolygonList() )
        {
            if( pol.containsPoint( pos, Qt::FillRule::OddEvenFill ) )
                return o;
            pIndex++;
        }
    }
    return ObjectPolygon();
}

// Setzt das Objekt an einer bestimmten Position im Bild.
// Fals an der Position bereits ein Objekt existiert, so wird nur die Objekt
// ID gesetzt.
//  pos: die Position
//  object: Das neue Objekt
//  center: Der Mittelpunkt des neuen Objektes
//  rotation: Die Drehung des neuen Objektes um den Mittelpunkt
void Frame::setObjectAt( QPoint pos, ObjectPolygon copy, QPoint center, float rotation )
{
    needSave = 1;
    for( ObjectPolygon o : objects )
    {
        for( QPolygon pol : o->getPolygonList() )
        {
            if( pol.containsPoint( pos, Qt::FillRule::OddEvenFill ) )
            {
                bool needQ = false;
                if( copy->getId() != "-1" )
                {
                    for( ObjectPolygon o2 : objects )
                    {
                        if( o2 != o && o2->getId() == copy->getId() )
                            needQ = 1;
                    }
                }
                o->setId( copy->getId() );
                if( needQ )
                {
                    QMessageBox::StandardButton reply = QMessageBox::question(0, "Warnung", "Auf diesem Bild gibt es bereits ein Objekt mit der gleichen Id. Möchten sie beide Objekte vereinen?",
                                                                              QMessageBox::Yes|QMessageBox::No);
                    if (reply == QMessageBox::Yes)
                        connectObjects( copy->getId() );
                }
                return;
            }
        }
    }
    QMatrix m;
    m.QMatrix::translate( pos.x(), pos.y() );
    m.rotate( rotation );
    m.translate( -center.x(), -center.y() );
    QList< QPolygon > newPols;
    for( QPolygon p : copy->getPolygonList() )
        newPols.append( m.map( p ) );
    bool needQ = false;
    if( copy->getId() != "-1" )
    {
        for( ObjectPolygon o2 : objects )
        {
            if( o2->getId() == copy->getId() )
                needQ = 1;
        }
    }
    addObject( copy->getId(), copy->isTruncated(), newPols );
    if( needQ )
    {
        QMessageBox::StandardButton reply = QMessageBox::question(0, "Warnung", "Auf diesem Bild gibt es bereits ein Objekt mit der gleichen Id. Möchten sie beide Objekte vereinen?",
                                                                  QMessageBox::Yes|QMessageBox::No);
        if (reply == QMessageBox::Yes)
            connectObjects( copy->getId() );
    }
}

// Macht ein Objekt an einer bestimmten Stelle sichtbar oder unsichtbar
//  pos: Die Position
void Frame::selectObjectAt( QPoint pos )
{
    for( ObjectPolygon o : objects )
    {
        for( QPolygon pol : o->getPolygonList() )
        {
            if( pol.containsPoint( pos, Qt::FillRule::OddEvenFill ) )
                o->setSelected( !o->isSelected() );
        }
    }
}

// Schneidet ein Polygon am rand des Bildes ab und gibt das Ergebnis zurück
//  uncliped: Das nicht abgeschnittene Polygon
QPolygon Frame::clipPolygon( QPolygon uncliped ) const
{
    QPolygon cliped = uncliped.intersected( QPolygon( QRect( QPoint( 0, 0 ), size ), true ) );
    if( !cliped.isEmpty() )
        cliped.pop_back();
    return cliped;
}

// Vereiniegt verschiedene Objekte mit der gleichen Objekt ID zu einem
//  id: Die ID, deren Objekte vereiniegt werden sollen
void Frame::connectObjects( QString id )
{
    needSave = 1;
    ObjectPolygon o;
    int oAnz = objects.count();
    for( int i = 0; i < oAnz; i++ )
    {
        ObjectPolygon o2 = objects.at( i );
        if( o2 != o && o2->getId() == id )
        {
            if( o.isNull() )
                o = o2;
            else
            {
                o->getPolygonList().append( o2->getPolygonList() );
                o->setTruncated( o->isTruncated() || o2->isTruncated() );
                o->setSelected( o->isSelected() || o2->isSelected() );
                objects.removeAt( i );
                i--;
                oAnz--;
            }
        }
    }
}

// Zerteilt ein Objekt, welches aus mehreren Polygonen besteht
//  o: Das Objekt welches zerteilt werden soll
void Frame::disconnectObject( ObjectPolygon o )
{
    needSave = 1;
    for( QPolygon p : o->getPolygonList() )
    {
        QList< QPolygon > plist;
        plist.append( p );
        addObject( o->getId(), o->isTruncated(), plist );
    }
    removeObject( o );
}

// Gibt 1 zurück, falls die Annotation zu dem Bild gespeichert werden muss
// und setzt den Flag auf 0, so dass erst nach einer änderung wieder 1 zurückgegeben wird
bool Frame::wasChangedSinceLastSave()
{
    bool ret = needSave;
    needSave = 0;
    return ret;
}
