//==============================================
//  copyright            : (C) 2003-2005 by Will Stokes
//==============================================
//  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.
//==============================================
//Systemwide includes
#include <qpixmap.h>
#include <qimage.h>
#include <qstring.h>
#include <qtextstream.h>
#include <qdom.h>
#include <qdir.h>
#include <qfileinfo.h>
#include <qregexp.h>

//Projectwide includes
#include "photo.h"
#include "subalbum.h"
#include "album.h"
#include "tools/fileTools.h"
#include "tools/imageTools.h"
#include "tools/xmlTools.h"
#include "tools/md5.h"
#include "../config.h"

//==============================================
Photo::Photo(Subalbum* subalbum, Photo* prev, int photoNumber)
{
  //set subalbum pointer
  this->subalbum = subalbum;

  //set prev pointer
  this->prev = prev;
  
  //set next pointer to NULL, new photos are always
  //inserted at the end of collections
  next = NULL;
  
  //set initial photo and subalbum numbers
  initialPhotoNumber = photoNumber;
  initialSubalbumNumber = subalbum->getSubalbumNumber();

  //set default to the empty string
  description = QString::null;

  //set thumbnail image
  thumbnailImage = NULL;

  //set filenames and checksums to null until the actual photo data has been set
  imageLocation     = QString::null;
  imageChecksum     = QString::null;
  
  slideshowLocation = QString::null;
  slideshowChecksum = QString::null;
  
  thumbnailLocation = QString::null;
  thumbnailChecksum = QString::null;

  //a default photo is not interesting. once the 
  //actual photo data or description files have
  //been reset the photo will need to be saved.
  needsSaving = false;
  
  //by default a photos are assumed to be saved. 
  //new photos are setup with a uniqueID and we'll set this bool false there
  everSaved = true;
  
  //photo not recently reverted
  recentlyReverted = false;
}
//==============================================
Photo::~Photo()
{
  //free the thumbnail image
  delete thumbnailImage;
}
//==============================================
QImage* Photo::getThumbnailImage() { return thumbnailImage; }
//==============================================
bool Photo::constructSmallerImages()
{
  //construct and save slideshow and thumbnail images
  QImage TslideshowImage, TthumbnailImage;
  constructImages( imageLocation, TslideshowImage, TthumbnailImage );  
  TslideshowImage.save( slideshowLocation, "JPEG", 95 );
  TthumbnailImage.save( thumbnailLocation, "JPEG", 95 );

  //load up thumbnail image
  delete thumbnailImage;
  thumbnailImage = new QImage(thumbnailLocation);

  //image is being stored in temp location, needs saving!
  needsSaving = true;

  //set the subalbum as being modified and return
  subalbum->setModified();
  return true;
}
//==============================================
bool Photo::setImage(QString imageName,
                     QString slideshowName,
                     QString thumbnailName)
{  
  //set filenames, we'll lazily compute MD5 checksums for files when saving
  imageLocation     = imageName;
  slideshowLocation = slideshowName;
  thumbnailLocation = thumbnailName;
  
  //load thumbnail image
  delete thumbnailImage;
  thumbnailImage = new QImage(thumbnailName);
  if(thumbnailImage->isNull()) return false;
  
  //image just loaded, no changes yet
  needsSaving = false;
  return true;
}
//==============================================
bool Photo::setImage(QString imageName, int uniqueID)
{ 
  //this is a new photo, use a unique ID to construct temporary filenames
  setEverSaved(false);
  initialSubalbumNumber = 0;
  initialPhotoNumber = uniqueID;

  QString tmpDir = subalbum->getAlbum()->getTmpDir();
  imageLocation     = QString("%1/%2_%3.jpg")          .arg(tmpDir).arg(initialSubalbumNumber).arg(initialPhotoNumber);
  slideshowLocation = QString("%1/%2_%3_slideshow.jpg").arg(tmpDir).arg(initialSubalbumNumber).arg(initialPhotoNumber);
  thumbnailLocation = QString("%1/%2_%3_thumb.jpg")    .arg(tmpDir).arg(initialSubalbumNumber).arg(initialPhotoNumber);
  
  //if image in jpeg format simply copy file over
  if( isJpeg(imageName) )
  {
    copyFile( imageName, imageLocation );
  }
  //otherwise we must load it up and save it out as jpeg
  else
  {
    //if unable to open image at all using Qt then giveup
    QImage tempImage(imageName);
    if( tempImage.isNull() ) { return false; }
    
    //save out as jpeg
    tempImage.save( imageLocation, "JPEG", 95 );
  }  
  
  //construct smaller iamges
  return constructSmallerImages();
}
//==============================================
bool Photo::setImage(QString editedImageFilename)
{
  //if the image has been saved then simply change the image,slideshow/thubnail 
  //filename handles to point to the temporary directory. We don't need to worry about backing up the
  //image because the new version will be written to the temporary directory and the save location
  if( getEverSaved() )
  {
    imageLocation = subalbum->getAlbum()->getTmpDir() + 
                    QString("/%1_%2.jpg").arg(initialSubalbumNumber).arg(initialPhotoNumber);
    slideshowLocation = subalbum->getAlbum()->getTmpDir() + 
                        QString("/%1_%2_slideshow.jpg").arg(initialSubalbumNumber).arg(initialPhotoNumber);
    thumbnailLocation = subalbum->getAlbum()->getTmpDir() + 
                        QString("/%1_%2_thumb.jpg").arg(initialSubalbumNumber).arg(initialPhotoNumber);
  }
  //otherwise image has never been saved, make sure original form has been backed up!
  else
  {
    QString tempOrigLocation = imageLocation;
    tempOrigLocation.truncate( imageLocation.length() - 4 );
    tempOrigLocation = tempOrigLocation + "_orig.jpg";
       
    QDir tmpDir;
    if(!tmpDir.exists( tempOrigLocation ) )
    { copyFile( imageLocation, tempOrigLocation ); }    
  }
  
  //copy over full size image
  copyFile( editedImageFilename, imageLocation );
  
  //reset revert flag since image has not been modified
  recentlyReverted = false;
  
  //construct smaller iamges
  return constructSmallerImages();
}
//==============================================
QString Photo::getImageFilename()             { return imageLocation;     }
QString Photo::getSlideshowFilename()         { return slideshowLocation; }
QString Photo::getThumbnailFilename()         { return thumbnailLocation; }

void Photo::setImageFilename(QString val)     { imageLocation = val;     }
void Photo::setSlideshowFilename(QString val) { slideshowLocation = val; }
void Photo::setThumbnailFilename(QString val) { thumbnailLocation = val; }
//==============================================
QString Photo::getImageChecksum()             { return imageChecksum; }
QString Photo::getThumbnailChecksum()         { return thumbnailChecksum; }
QString Photo::getSlideshowChecksum()         { return slideshowChecksum; }

void Photo::setImageChecksum(QString val)     { imageChecksum = val; }
void Photo::setThumbnailChecksum(QString val) { thumbnailChecksum = val; }
void Photo::setSlideshowChecksum(QString val) { slideshowChecksum = val; }
//==============================================
QString Photo::getDescription() { return QString(description); }
//==============================================
void Photo::setDescription(QString val)
{
  //set empty strings as null, takes up less space and necessary
  //to check for string modification
  if( val.isEmpty() )
    val = QString::null;

  if(description.compare(val) != 0)
  {
    description = val;
    subalbum->setModified();
  }
}
//==============================================
Photo* Photo::getPrev() { return prev; }
Photo* Photo::getNext() { return next; }
//==============================================
void Photo::setPrev(Photo* val) 
{ 
  prev = val;
  subalbum->setModified();
}
//==============================================
void Photo::setNext(Photo* val) 
{ 
  next = val;
  subalbum->setModified();
}
//==============================================
QDateTime* Photo::importFromDisk(QDomNode* root)
{
  //create modified date/time object for returning
  QDateTime* modified = new QDateTime[3];

  QDomNode node = root->firstChild();
  QDomText val;
  while( !node.isNull() )
  {
    //------------------------------------------------------------
    //photo description
    if( node.isElement() && node.nodeName() == "description" )
    {
      val = node.firstChild().toText();
      if(!val.isNull())
        description = val.nodeValue();
     description.replace("\\&quot;","\"");
    }
    //------------------------------------------------------------
    //image information
    else if( node.isElement() && node.nodeName() == "image" )
    {
      QDomNode childNode = node.firstChild();
      while( !childNode.isNull() )
      {
        //------------------------------------------------------------
        if( childNode.isElement() && childNode.nodeName() == "md5" )
        {
          val = childNode.firstChild().toText();
          if(!val.isNull())
            imageChecksum = val.nodeValue();
        }
        //------------------------------------------------------------
        else if( childNode.isElement() && childNode.nodeName() == "modified" )
        {
          val = childNode.firstChild().toText();

          //split value based on spaces, should be 7 fields
          QStringList vals = QStringList::split( QRegExp(" "), val.nodeValue() );
          int i=0;
          int intVals[7];
          QStringList::Iterator it;
          for ( it = vals.begin(); it != vals.end(); ++it )
          {
            //sanity check incase more fields are provided than there should be
            if(i >6)
              break;

            intVals[i] = QString(*it).toInt();
            i++;
          }
          modified[0].setDate( QDate(intVals[0], intVals[1], intVals[2]) );
          modified[0].setTime( QTime(intVals[3], intVals[4], intVals[5], intVals[6]) );
        }
        //------------------------------------------------------------
        childNode = childNode.nextSibling();
      }
    }
    //------------------------------------------------------------
    //slideshow information
    else if( node.isElement() && node.nodeName() == "slideshow" )
    {
      QDomNode childNode = node.firstChild();
      while( !childNode.isNull() )
      {
        //------------------------------------------------------------
        if( childNode.isElement() && childNode.nodeName() == "md5" )
        {
          val = childNode.firstChild().toText();
          if(!val.isNull())
            slideshowChecksum = val.nodeValue();
        }
        //------------------------------------------------------------
        else if( childNode.isElement() && childNode.nodeName() == "modified" )
        {
          val = childNode.firstChild().toText();

          //split value based on spaces, should be 6 fields
          QStringList vals = QStringList::split( QRegExp(" "), val.nodeValue() );
          int i=0;
          int intVals[7];
          QStringList::Iterator it;
          for ( it = vals.begin(); it != vals.end(); ++it )
          {
            //sanity check incase more fields are provided than there should be
            if(i >6)
              break;

            intVals[i] = QString(*it).toInt();
            i++;
          }
          modified[1].setDate( QDate(intVals[0], intVals[1], intVals[2]) );
          modified[1].setTime( QTime(intVals[3], intVals[4], intVals[5], intVals[6]) );
        }
        //------------------------------------------------------------
        childNode = childNode.nextSibling();
      }
    }
    //------------------------------------------------------------
    //slideshow information
    else if( node.isElement() && node.nodeName() == "thumb" )
    {
      QDomNode childNode = node.firstChild();
      while( !childNode.isNull() )
      {
        //------------------------------------------------------------
        if( childNode.isElement() && childNode.nodeName() == "md5" )
        {
          val = childNode.firstChild().toText();
          if(!val.isNull())
            thumbnailChecksum = val.nodeValue();
        }
        //------------------------------------------------------------
        else if( childNode.isElement() && childNode.nodeName() == "modified" )
        {
          val = childNode.firstChild().toText();

          //split value based on spaces, should be 6 fields
          QStringList vals = QStringList::split( QRegExp(" "), val.nodeValue() );
          int i=0;
          int intVals[7];
          QStringList::Iterator it;
          for ( it = vals.begin(); it != vals.end(); ++it )
          {
            //sanity check incase more fields are provided than there should be
            if(i >6)
              break;

            intVals[i] = QString(*it).toInt();
            i++;
          }
          modified[2].setDate( QDate(intVals[0], intVals[1], intVals[2]) );
          modified[2].setTime( QTime(intVals[3], intVals[4], intVals[5], intVals[6]) );
        }
        //------------------------------------------------------------
        childNode = childNode.nextSibling();
      }
    }
    //------------------------------------------------------------
    //------------------------------------------------------------
    //Handle md5 info as specified in 1.0a and 1.0a2 xml format
    //image md5
    else if( node.isElement() && node.nodeName() == "imageMD5" )
    {
      val = node.firstChild().toText();
      if(!val.isNull())
        imageChecksum = val.nodeValue();
    }
    //------------------------------------------------------------
    //slideshow md5
    else if( node.isElement() && node.nodeName() == "slideMD5" )
    {
      val = node.firstChild().toText();
      if(!val.isNull())
        slideshowChecksum = val.nodeValue();
    }
    //------------------------------------------------------------
    //thumbnail md5
    else if( node.isElement() && node.nodeName() == "thumbMD5" )
    {
      val = node.firstChild().toText();
      if(!val.isNull())
        thumbnailChecksum = val.nodeValue();
    }
    //------------------------------------------------------------
    //------------------------------------------------------------
    //advance to next node
    node = node.nextSibling();
    //------------------------------------------------------------
  }

  //return modification dates read in
  return modified;
}
//==============================================
void Photo::exportToXML(QTextStream& stream)
{
  QFileInfo info;

  //write photo information
  stream << "    <photo>\n";
  //-----
  stream << "      <description>" << fixXMLString(description) << "</description>\n";
  //-----
  //full image
  info.setFile( getImageFilename() );
  QDateTime modified = info.lastModified();
  stream << "      <image>\n";
  stream << "        <md5>" << fixXMLString(imageChecksum) << "</md5>\n";
  stream << "        <modified>";
  stream << modified.date().year() << " ";
  stream << modified.date().month() << " ";
  stream << modified.date().day() << " ";
  stream << modified.time().hour() << " ";
  stream << modified.time().minute() << " ";
  stream << modified.time().second() << " ";
  stream << modified.time().msec() << "</modified>\n";
  stream << "      </image>\n";
  //-----
  //slidehow image
  info.setFile( getSlideshowFilename() );
  modified = info.lastModified();
  stream << "      <slideshow>\n";
  stream << "        <md5>" << fixXMLString(slideshowChecksum) << "</md5>\n";
  stream << "        <modified>";
  stream << modified.date().year() << " ";
  stream << modified.date().month() << " ";
  stream << modified.date().day() << " ";
  stream << modified.time().hour() << " ";
  stream << modified.time().minute() << " ";
  stream << modified.time().second() << " ";
  stream << modified.time().msec() << "</modified>\n";
  stream << "      </slideshow>\n";
  //-----
  //thumbnail image
  info.setFile( getThumbnailFilename() );
  modified = info.lastModified();
  stream << "      <thumb>\n";
  stream << "        <md5>" << fixXMLString(thumbnailChecksum) << "</md5>\n";
  stream << "        <modified>";
  stream << modified.date().year() << " ";
  stream << modified.date().month() << " ";
  stream << modified.date().day() << " ";
  stream << modified.time().hour() << " ";
  stream << modified.time().minute() << " ";
  stream << modified.time().second() << " ";
  stream << modified.time().msec() << "</modified>\n";
  stream << "      </thumb>\n";
  //-----
  stream << "    </photo>\n";
}
//==============================================
void Photo::rotate90()         { applyTransformation( ROTATE_90 );  }
void Photo::rotate270()        { applyTransformation( ROTATE_270 ); }
void Photo::flipHorizontally() { applyTransformation( FLIP_H );     }
void Photo::flipVertically()   { applyTransformation( FLIP_V );     }
//==============================================
void Photo::applyTransformation(TRANSFORM_CODE transformation)
{  
  //backup old filename
  QString oldName = imageLocation;
   
  //if the image did not previously need saving, 
  //reset filenames to point to temporary location and
  //immediately perform transformation
  if(!needsSaving)
  {
    imageLocation = subalbum->getAlbum()->getTmpDir() + QString("/%1_%2.jpg")
                                                                .arg(initialSubalbumNumber)
                                                                .arg(initialPhotoNumber);
    slideshowLocation = subalbum->getAlbum()->getTmpDir() + QString("/%1_%2_slideshow.jpg")
                                                                .arg(initialSubalbumNumber)
                                                                .arg(initialPhotoNumber);
    thumbnailLocation = subalbum->getAlbum()->getTmpDir() + QString("/%1_%2_thumb.jpg")
                                                                .arg(initialSubalbumNumber)
                                                                .arg(initialPhotoNumber);
    transformImage( oldName, imageLocation, transformation );
  }
  else
  {
    //images that need saving already exist in the temporary directory
    //this poses two problems:
    //1.) fast jpeg transformations cannot be done in place, so we'll employ an
    //    intermediate image
    QString intermediateName = QString("%1_intermdiate.jpg").arg(oldName);
    transformImage( oldName, intermediateName, transformation );    
    
    //2.) If the photo has never been saved and an orig file in the temporary
    //    directory does not exist then the current file is the original version. we
    //    must make sure that this original photo is maintained using an orig file so
    //    in the future users can revert the photo to it's original form.
    QString origName = subalbum->getAlbum()->getTmpDir() + QString("/0_%1_orig.jpg")
      .arg(initialPhotoNumber);      
    QDir tmpDir;
    if( !getEverSaved() && !tmpDir.exists(origName) )
    {
      moveFile( oldName, origName );
      moveFile( intermediateName, imageLocation );
    }
    else
    {
      moveFile( intermediateName, imageLocation );
    }
  }
  
  //image now modified from original form so orig file needs to be kept
  recentlyReverted = false;
  
  //construct smaller iamges
  constructSmallerImages();
}
//==============================================
bool Photo::getNeedsSavingVal()               { return needsSaving; }
void Photo::setNeedsSavingVal(bool val)       { needsSaving = val; }
//==============================================
bool Photo::getEverSaved()                    { return everSaved; }
void Photo::setEverSaved(bool val)            { everSaved = val; }
//==============================================
bool Photo::revertPossible()
{
  //if photo not recently reverted and orig and current filenames differ
  QString newName = getImageFilename();
  QString origName = originalImageFilename();
  
  return ( !recentlyReverted &&
           origName.compare( newName ) !=0 );
}
//==============================================
bool Photo::getRecentlyReverted()
{
  return recentlyReverted;
}
//==============================================
void Photo::setRecentlyReverted(bool val)
{
  recentlyReverted = val;
}
//==============================================
void Photo::revertPhoto()
{
  //ignore if revert is not possible
  if(!revertPossible())
    return;
  
  //set image to reverted form
  QString origName = originalImageFilename();
  setImage( origName );
  
  //recently reverted, orig file should be
  //removed during next save since it's redundant
  recentlyReverted = true;
}
//==============================================
QString Photo::originalImageFilename()
{
  //determining the an images original filename is tricky
  //if the photo has never been saved check for presence of an _orig file,
  //otherwise use the current filename since the photo has not yet been modified
  if( !getEverSaved() )
  {
    QString tempOrigLocation = imageLocation;
    tempOrigLocation.truncate( imageLocation.length() - 4 );
    tempOrigLocation = tempOrigLocation + "_orig.jpg";
    
    QDir tmpDir;
    if(tmpDir.exists( tempOrigLocation ) )
      return tempOrigLocation;
    else
      return imageLocation;
  }
  //if the photo was previously saved, it's original form could either be:
  //1.) the permanant storage location + _orig
  //2.) the permanant storage location
  else
  {
    QString storedOrigLocation = subalbum->getAlbum()->getSaveLocation() +  
      QString("/img/%1/%2_orig.jpg").arg(initialSubalbumNumber).arg(initialPhotoNumber);
   
    QString lastSavedLocation = subalbum->getAlbum()->getSaveLocation() +  
      QString("/img/%1/%2.jpg").arg(initialSubalbumNumber).arg(initialPhotoNumber);    
    
    QDir tmpDir;
    if(tmpDir.exists( storedOrigLocation ) )
      return storedOrigLocation;
    else
      return lastSavedLocation;
  }
}
//==============================================
int Photo::getInitialPhotoNumber()            { return initialPhotoNumber; }
void Photo::setInitialPhotoNumber(int val)    { initialPhotoNumber = val; }

int Photo::getInitialSubalbumNumber()         { return initialSubalbumNumber; }
void Photo::setInitialSubalbumNumber(int val) { initialSubalbumNumber = val; }
//==============================================
