#include "sequenz.h"
#include <tinyxml2.h>
#include <QtConcurrent/QtConcurrent>
#include <QProgressDialog>
#include <fstream>

Sequenz::Sequenz( QString p, QList< Kamera* > c, QList< Object > o )
    : ref( 1 ),
    path( p ),
    frameIndex( 0 ),
    cameraIndex( 0 ),
    cams( c ),
    objects( o )
{
    addClass( "packingbox" );
}

Sequenz::~Sequenz()
{
    for( auto k = cams.begin(); k != cams.end(); k++ )
        (*k)->refRelease();
}

// Gibt eine Liste mit allen bekannten Objekt Klassen zurück
QList< Sequenz::SegmentationClass > Sequenz::getClasses() const
{
    return classes;
}

// Setzt den Namen der Objekt Klasse mit der ID id auf name
bool Sequenz::setClassName( int id, QString name )
{
    int i = getClassId( name );
    if( i == id )
        return true;
    if( i != -1 )
        return false;
    for( auto c = classes.begin(); c != classes.end(); c++ )
    {
        if( c->id == id )
        {
            c->name = name;
            return true;
        }
    }
    return false;
}

// Prüft, ob bereits annotierte Objekte in der Sequenz vorhanden sind
QString Sequenz::getClassName( int id ) const
{
    for( auto c = classes.begin(); c != classes.end(); c++ )
    {
        if( c->id == id )
            return c->name;
    }
    return "NULL";
}

// Gibt den Namen der Objekt Klasse mit der ID id zurück
int Sequenz::getClassId( QString name ) const
{
    for( auto c = classes.begin(); c != classes.end(); c++ )
    {
        if( c->name == name )
            return c->id;
    }
    return -1;
}

// Gibt die ID der Objekt Klasse mit dem Namen name zurück
int Sequenz::getClassOfObject( QString id ) const
{
    for( auto obj = objects.begin(); obj != objects.end(); obj++ )
    {
        if( obj->getId() == id )
            return obj->getClassId();
    }
    return -1;
}

// Gibt die ID der Klasse des Objektes mit der ID objektId zurück
void Sequenz::setClassOfObject( QString id, int classId )
{
    for( auto obj = objects.begin(); obj != objects.end(); obj++ )
    {
        if( obj->getId() == id )
            obj->setClassId( classId );
    }
}

// Setzt die ID der Klasse des Objektes mit der ID objektId auf classId
bool Sequenz::hasAnnotatedObjects() const
{
    for( Object o : objects )
    {
        if( o.getId() != "-1" )
            return true;
    }
    return false;
}

// Fügt eine neue Objekt Klasse mit Namen name hinzu
int Sequenz::addClass( QString name )
{
    if( getClassId( name ) != -1 )
        return -1;
    int max = -1;
    for( auto c = classes.begin(); c != classes.end(); c++ )
    {
        if( c->id > max )
            max = c->id;
    }
    max++;
    classes.append( { max, name } );
    return max;
}

// Entfernt die Objektklasse mit der ID id
bool Sequenz::removeClass( int id )
{
    for( auto c = classes.begin(); c != classes.end(); c++ )
    {
        if( c->id == id )
        {
            classes.erase( c );
            int newId = 0;
            if( classes.size() > 0 )
                newId = classes.first().id;
            for( auto obj = objects.begin(); obj != objects.end(); obj++ )
            {
                if( obj->getClassId() == id )
                    obj->setClassId(newId);
            }
            return true;
        }
    }
    return false;
}

// Wählt das frame-te Bild der cam-ten Kamera aus
void Sequenz::selectFrame( int cam, int frame )
{
    cameraIndex = cam;
    frameIndex = frame;
}

// Gibt das ausgewählte Bild zurück
Frame *Sequenz::getFrame() const
{
    return cams.at( cameraIndex )->getFrame( frameIndex );
}

// Gibt das vom ausgewählten Bild um offset Bilder entfernte Bild zurück
Frame *Sequenz::getFrame( int offset ) const
{
    int currCam = cameraIndex;
    int currFrame = frameIndex;
    int oldCOunt = offset;
    do
    { // Schleife durch alle bilder (in die angegebene Richtung) bis ein Bild gefunden wird auf dem das Objekt vorkommt
        if( currFrame >= cams.at( currCam )->getChildCount() )
        {
            currCam++;
            if( currCam >= cams.size() )
               currCam = 0;
            currFrame = 0;
        }
        Frame *f = cams.at( currCam )->getFrame( currFrame );
        offset--;
        currFrame++;
        if( offset <= 0 ) // das gesuchte Bild wurde erreicht
            return f;
        if( currFrame == frameIndex && currCam == cameraIndex )
        {
            if( oldCOunt == offset )
                break;
        }
    } while( offset > 0 );
    return 0;
}

// Gibt den Index der ausgewählten Kamera zurück
int Sequenz::getSelectedCamera() const
{
    return cameraIndex;
}

// Gibt den Index des Ausgewählten Bildes (innerhalb der Kamera) zurück
int Sequenz::getSelectedFrame() const
{
    return frameIndex;
}

// Gibt eine Liste mit Kameras zurück
const QList< Kamera* > &Sequenz::getCameras() const
{
    return cams;
}

// Wählt das nachfolgende Bild aus
void Sequenz::nextFrame()
{
    if( !hasNextFrame() )
        return;
    if( ++frameIndex == cams.at( cameraIndex )->getChildCount() )
    {
        frameIndex = 0;
        cameraIndex++;
    }
}

// Wählt das vorherige Bild aus
void Sequenz::previousFrame()
{
    if( !hasPreviousFrame() )
        return;
    if( --frameIndex < 0 )
        frameIndex = cams.at( --cameraIndex )->getChildCount() - 1;
}

// Gibt 1 zurück, wenn in der Sequenz ein nachfolgendes Bild existiert
bool Sequenz::hasNextFrame() const
{
    return cameraIndex < cams.count() - 1 || frameIndex < cams.at( cameraIndex )->getChildCount() - 1;
}

// Gibt 1 zurück, wenn in der Sequenz ein vorheriges Bild existiert
bool Sequenz::hasPreviousFrame() const
{
    return frameIndex > 0 || cameraIndex > 0;
}

// Gibt eine Liste mit allen vergebenen ObjektIds zurück
QList< QString > Sequenz::getObjectNames() const
{
    QList<QString> result;
    foreach( Object obj, objects )
        result.append( obj.getId() );
    return result;
}

// Gibt von dem ausgewählten Bild aus das count-nächste Bild von dem Objekt mit ID objektId zurück.
QImage Sequenz::previousObjectImage( QString packetName, int count ) const
{
    int currCam = cameraIndex;
    int currFrame = frameIndex;
    QImage img;
    int dir = 1;
    if( count < 0 )
    { // falls count negativ ist, wird weiter vorne in der sequenz gesucht
        dir = -1;
        count = -count;
    }
    int oldCOunt = count;
    do
    { // Schleife durch alle bilder (in die angegebene Richtung) bis ein Bild gefunden wird auf dem das Objekt vorkommt
        if( (currFrame += dir) < 0 || currFrame >= cams.at( currCam )->getChildCount() )
        {
            if( (currCam += dir) < 0 || currCam >= cams.size() )
            {
                currCam = cams.size() - 1;
                if( dir > 0 )
                    currCam = 0;
            }
            currFrame = cams.at( currCam )->getChildCount() - 1;
            if( dir > 0 )
                currFrame = 0;
        }
        Frame *f = cams.at( currCam )->getFrame( currFrame );
        if( f->hasObject( packetName ) )
        {
            count--;
            if( count <= 0 ) // das gesuchte Bild wurde erreicht
                img = f->getObjectImage( packetName );
        }
        if( currFrame == frameIndex && currCam == cameraIndex )
        {
            if( oldCOunt == count )
                break;
        }
    } while( count > 0 );
    return img;
}

// Gibt die größte vergebene Objekt Id zurück;
int Sequenz::getMaxObjectId() const
{
    int max = -1;
    for( auto o : objects )
    {
        if( o.getId().toInt() > max )
            max = o.getId().toInt();
    }
    return max;
}

// Fügt eine neue ObjektID mit der Klassen ID classID hinzu
void Sequenz::addObjectName( QString name, int classId )
{
    if( objects.indexOf( Object( name, classId ) ) < 0 )
        objects.append( Object( name, classId ) );
}

// Fügt eine neue ObjektID hinzu
void Sequenz::addObjectName( QString name )
{
    if( classes.size() > 0 )
        addObjectName( name, classes.first().id );
    else
        addObjectName( name, 0 );
}

// Speichert die Sequenz.
//  status: Ein Label, in denen Fortschrittsinformationen geschrieben werden
void Sequenz::saveToPath( QLabel *status ) const
{
    int numOps = 0;
    for( Kamera *k : cams )
        numOps += k->getChildCount();
    QProgressDialog progress( "Annotation wird gespeichert...", "", 0, numOps );
    progress.setWindowModality(Qt::WindowModal);
    progress.setCancelButton( 0 );

    if( !QFile( path + "/ImageSets/Main/train.txt" ).exists() )
        QDir( path + "/ImageSets/Main" ).mkpath(".");
    std::ofstream trainTxt( (path + "/ImageSets/Main/train.txt").toStdString().c_str() );
    for( int cam = 0; cam < cams.size(); cam++ )
    { // Schleife durch alle Kameras
        Kamera *currentCam = cams.at( cam );
        int frameCount = currentCam->getChildCount();
        for( int frame = 0; frame < frameCount; frame++ )
        { // Schleife durch alle Bilder
            if( status )
            {
                QString text = "save Annotations " + QString::number( cam + 1 ) + "/" + QString::number( cams.size() ) + " " + QString::number( frame + 1 ) + "/" + QString::number( cams.at( cam )->getChildCount() );
                QMetaObject::invokeMethod( status, "setText",  Q_ARG( QString, text ) );
            }
            Frame *currentFrame = currentCam->getFrame( frame );
            if( !currentFrame->isNotAnnotated() || currentFrame->getObjects().size() > 0 )
            {
                trainTxt << ( currentFrame->getName().mid( 0, currentFrame->getName().indexOf( "." ) ) + "\n" ).toStdString().c_str();
            }
            if( currentFrame->wasChangedSinceLastSave() )
            { // Falls das Bild seid dem letzten speichern verändert wurde
                tinyxml2::XMLDocument doc; // Erstelle die xml datei
                doc.LinkEndChild( doc.NewDeclaration( "xml version=\"1.0\" " ) );
                tinyxml2::XMLElement *annotation = doc.NewElement( "annotation");
                annotation->LinkEndChild( doc.NewElement( "folder" ) )->LinkEndChild( doc.NewText( "VOC2007" ) );
                annotation->LinkEndChild( doc.NewElement( "filename" ) )->LinkEndChild( doc.NewText( currentFrame->getName().toStdString().c_str() ) );
                tinyxml2::XMLElement *source = doc.NewElement( "source" );
                source->LinkEndChild( doc.NewElement( "database" ) )->LinkEndChild( doc.NewText( "The VOC2007 Database" ) );
                source->LinkEndChild( doc.NewElement( "annotation" ) )->LinkEndChild( doc.NewText( "PASCAL VOC 2007" ) );
                source->LinkEndChild( doc.NewElement( "image" ) )->LinkEndChild( doc.NewText( currentFrame->getName().toStdString().c_str() ) );
                annotation->LinkEndChild( source );
                annotation->LinkEndChild( doc.NewElement( "owner" ) )->LinkEndChild( doc.NewElement( "name" ) )->LinkEndChild( doc.NewText( "Kolja Strohm" ) );
                annotation->LinkEndChild( doc.NewElement( "camera_id" ) )->LinkEndChild( doc.NewText( currentCam->getName().toStdString().c_str() ) );
                tinyxml2::XMLElement *size = doc.NewElement( "size" );
                QImage img = currentFrame->getImage();
                size->LinkEndChild( doc.NewElement( "width" ) )->LinkEndChild( doc.NewText( std::to_string( img.width() ).c_str() ) );
                size->LinkEndChild( doc.NewElement( "height" ) )->LinkEndChild( doc.NewText( std::to_string( img.height() ).c_str() ) );
                size->LinkEndChild( doc.NewElement( "depth" ) )->LinkEndChild( doc.NewText( std::to_string( img.depth() ).c_str() ) );
                annotation->LinkEndChild( size );
                annotation->LinkEndChild( doc.NewElement( "segmented" ) )->LinkEndChild( doc.NewText( "0" ) );
                annotation->LinkEndChild( doc.NewElement( "timestamp" ) )->LinkEndChild( doc.NewText( currentFrame->getTimestamp().toStdString().c_str() ) );
                QList<ObjectPolygon> objects = currentFrame->getObjects();
                for( ObjectPolygon o : objects )
                { // Schleife durch alle Objekte
                    tinyxml2::XMLElement *object = doc.NewElement( "object" );
                    object->LinkEndChild( doc.NewElement( "name" ) )->LinkEndChild( doc.NewText( getClassName( getClassOfObject( o->getId() ) ).toStdString().c_str() ) );
                    object->LinkEndChild( doc.NewElement( "id" ) )->LinkEndChild( doc.NewText( o->getId().toStdString().c_str() ) );
                    object->LinkEndChild( doc.NewElement( "pose" ) )->LinkEndChild( doc.NewText( "Unspecified" ) );
                    object->LinkEndChild( doc.NewElement( "truncated" ) )->LinkEndChild( doc.NewText( std::to_string( (int)o->isTruncated() ).c_str() ) );
                    object->LinkEndChild( doc.NewElement( "difficult" ) )->LinkEndChild( doc.NewText( "0" ) );
                    int xMin = img.width();
                    int yMin = img.height();
                    int xMax = 0;
                    int yMax = 0;
                    std::vector< tinyxml2::XMLElement* > polygons;
                    for( QPolygon pol : o->getPolygonList() )
                    { // Schleife durch alle Polygone
                        tinyxml2::XMLElement *polygon = doc.NewElement( "polygon" );
                        for( QPoint point : pol )
                        { // Schleife durch alle Eckpunkte
                            tinyxml2::XMLElement *pointE = doc.NewElement( "point" );
                            pointE->LinkEndChild( doc.NewElement( "x" ) )->LinkEndChild( doc.NewText( std::to_string( point.x() ).c_str() ) );
                            pointE->LinkEndChild( doc.NewElement( "y" ) )->LinkEndChild( doc.NewText( std::to_string( point.y() ).c_str() ) );
                            polygon->LinkEndChild( pointE );
                            if( xMin > point.x() ) // ermittle die Bounding box
                                xMin = point.x();
                            if( yMin > point.y() )
                                yMin = point.y();
                            if( xMax < point.x() )
                                xMax = point.x();
                            if( yMax < point.y() )
                                yMax = point.y();
                        }
                        polygons.push_back( polygon );
                    }
                    tinyxml2::XMLElement *bndbox = doc.NewElement( "bndbox" );
                    bndbox->LinkEndChild( doc.NewElement( "xmin" ) )->LinkEndChild( doc.NewText( std::to_string( xMin ).c_str() ) );
                    bndbox->LinkEndChild( doc.NewElement( "ymin" ) )->LinkEndChild( doc.NewText( std::to_string( yMin ).c_str() ) );
                    bndbox->LinkEndChild( doc.NewElement( "xmax" ) )->LinkEndChild( doc.NewText( std::to_string( xMax ).c_str() ) );
                    bndbox->LinkEndChild( doc.NewElement( "ymax" ) )->LinkEndChild( doc.NewText( std::to_string( yMax ).c_str() ) );
                    object->LinkEndChild( bndbox );
                    for( tinyxml2::XMLElement *polygon : polygons )
                        object->LinkEndChild( polygon );
                    annotation->LinkEndChild( object );
                }
                if( objects.size() == 0 && currentFrame->isNotAnnotated() )
                    annotation->LinkEndChild( doc.NewElement( "noobject" ) )->LinkEndChild( doc.NewText( "need Annotation" ) );
                doc.LinkEndChild( annotation );
                QString name = currentFrame->getName();
                QDir().mkpath( path + "/Annotations" );
                doc.SaveFile( (path + "/Annotations/" + name.mid( 0, name.lastIndexOf( '.' ) ) + ".xml").toStdString().c_str() );
            }
            progress.setValue( progress.value() + 1 );
        }
    }
    trainTxt.close();
}

// Erhöht den Reference Counter um 1
void Sequenz::refNew()
{
    ref++;
}

// Verringert den Reference Counter um 1 (bei 0 löscht sich das Objekt selbst)
void Sequenz::refRelease()
{
    if( !--ref )
        delete this;
}
