/**
  \class CCamStreamApp
  \brief The CamStream main application class.

  This class is the main object for the CamStream application. It does
  the following:
   - creates the canvas, loads widgets;
   - determines available image file formats;
   - stores and retrieves user settings;


  The configuration is stored as an XML document. The structure still
  has to be defined fully, and is partially defined by sub-elements.

*/

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include <assert.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>

#include <qdir.h>
#include <qfile.h>
#include <qimage.h>
#include <qmessagebox.h>
#include <qstring.h>
#include <qstrlist.h>
#include <qtextstream.h>

#include "CamStreamApp.h"

static const char *conf_file = ".camstream";
static const char *upload_dir = ".camstream-upload";

/* This must follow FileTypeEnum! */
static struct {
  const char *extension;
  const char *format;
} FileTypes[] = {
  { "jpg", "JPEG" },
  { "png", "PNG" },
  { "ppm", "PPM" },
  { "bmp", "BMP" },
};

CCamStreamApp *CamApp = NULL; /* should be the only one */

/**
 \fn CCamStreamApp::CCamStreamApp(int argc, char *argv[])
 \brief Constructor
 
 Takes the \b argc and \b argv arguments from main().
 */

CCamStreamApp::CCamStreamApp(int argc, char *argv[])
	: QApplication(argc, argv)
{
   QString fullname;
   QDomDocument DD("Configuration");
   QDir Home;
  
   assert(CamApp == NULL);
   CamApp = this;

   Configuration.Root = DD; // Weird but true; if we just use an empty initialized DomDocument, it doesn't work.
   VideoDevices = CVideoCollector::Instance();

   VOpts.setAutoDelete(TRUE);
   VisiblePanels.setAutoDelete(TRUE);

   /* Set program wide defaults */
   InitFileTypes();

   fullname = QDir::homeDirPath();
   fullname.append("/");
   UploadDir = fullname;
   fullname.append(conf_file);
   ConfigFile.setName(fullname);

   UploadDir.append(upload_dir);
   Home = QDir::home();

   if (!Home.exists(UploadDir)) {   
     if (!Home.mkdir(UploadDir)) {
       switch(QMessageBox::warning(NULL, "upload directory", 
              "Failed to create temporary upload directory " + UploadDir,
              QMessageBox::Ok    | QMessageBox::Default, 
              QMessageBox::Abort | QMessageBox::Escape)) {
          case QMessageBox::Ok:
            UploadDir = "";
            break;
          case QMessageBox::Abort: // Escape
            exit(1);
            break;
       }
     }
   }

   /* Read user settings */
   ReadConfigFile();
}

/**
 \fn CCamStreamApp::~CCamStreamApp()
 \brief Destructor
 
 End of program; saves user settings (image formats, etc)
 */

CCamStreamApp::~CCamStreamApp()
{
   SaveConfigFile();
}

// private

/** Determine available formats for saving files, set basic filename and text color */
void CCamStreamApp::InitFileTypes()
{
   /* A friend of mine once told me this story:
   
      At work they used to have a mainframe or other heavy Unix machine
      that had a switch, labeled "Magic" and "More magic" (a simple,
      dual flip switch like an on-off button). Whenever they put the
      switch in the "Magic" position, the machine would crash instantly.
      In the "More magic" position it worked fine. Now for the weird part:
      there was only a single wire leading from the switch to the mainboard,
      and there it was connected to Ground. In other words: this wire was
      electrically speaking just floating in the air, with a switch on the
      end that wasn't connected to anything. Yet, if it was not in the
      right position, it would crash the machine.
      
      (Needless to say, this reeks of an Urban Legend)
   */
   QStrList lit;
   QStrListIterator *it;
   char *s;
   int c;

   snap_bitmask = 0;
   lit = QImage::outputFormats();
   it = new QStrListIterator(lit);
   s = it->toFirst();
   while (s != NULL) {
      for (c = 0; c < file_MAX; c++) {
         if (!strcmp(s, FileTypes[c].format))
           snap_bitmask |= (1 << c);
      }
      ++*it;
      s = it->current();
   }
}


void CCamStreamApp::ReadConfigFile()
{
   QDomNode n;
   QDomElement e;
   QDomDocument oldconfig;

   if (!ConfigFile.open(IO_ReadOnly))
     qWarning("Failed to open configuration file for reading.\n");
   else {
     if (!oldconfig.setContent(&ConfigFile))
       qWarning("Failed to parse configuration file.\n");
     ConfigFile.close();
   }

   // Create skeleton, and topnodes of configuration
   QDomElement config = Configuration.Root.documentElement();
   if (config.isNull())
     config = Configuration.Root.createElement("config");

   Configuration.Defaults = Configuration.Root.createElement("defaults");
   Configuration.VideoDevices = Configuration.Root.createElement("videodevices");

   // Walk elements, interpreting nodes
   n = oldconfig.documentElement().firstChild();
   while (!n.isNull()) {
      e = n.toElement(); // try to make it an Element
      if (!e.isNull()) { // Success... but what is it?
        if (e.tagName() == "defaults") { // Program defaults
//qDebug("Setting Configuration.Defaults");
          Configuration.Defaults = e; // for now
        }

        if (e.tagName() == "videodevices") {
          /* Configuration of video devices; basicly, we walk the existing tree,
             duplication nodes and appending them to Configuration.VideoDevices.
             This way new variables get in, and old ones disappear.
           */
          QDomNode vn;
          QDomElement ve;
          SVideoOptions *vopt;
          
          vn = e.firstChild();
          while (!vn.isNull()) {
             ve = vn.toElement();
             if (!ve.isNull()) {
//qDebug("Appending video options " + ve.tagName() + " name=" + ve.attribute("name"));
               vopt = new SVideoOptions(Configuration.Root);
               vopt->SetXML(ve);
               VOpts.append(vopt);
               // Append, so new options get in, and old ones disappear
               Configuration.VideoDevices.appendChild(vopt->GetXML());
             }
             vn = vn.nextSibling();
          }
        }

      }
      n = n.nextSibling();
   }
   
   config.appendChild(Configuration.Defaults);
   config.appendChild(Configuration.VideoDevices);
   Configuration.Root.appendChild(config); // This will become the root element; only one object is allowed!
//qDebug(Configuration.Root.toString());
}

void CCamStreamApp::SaveConfigFile()
{
   QTextStream *ts;
   QDomElement config;
   mode_t oldmask;

   /* Make sure our configuration cannot be read by others (FTP passwords!) */
   oldmask = umask(077);   
   if (!ConfigFile.open(IO_WriteOnly)) {
     qWarning("Failed to open configuration file for writing.\n");
   }
   else {
     /* Save config. We assume a consistent tree has been built in ReadConfigFile. */
     config = Configuration.Root.documentElement();
//qDebug(Configuration.Root.toString());
     ts = new QTextStream(&ConfigFile);
     Configuration.Root.save(*ts, 0);
     delete ts;
     ConfigFile.close();
   }
   umask(oldmask);
}



// public


QString CCamStreamApp::GetUploadTmpDir() const
{
   return UploadDir;
}


/** 
  \brief Return number of file formats
  
  This function returns the number of programmed file formats, including 
  the formats that are currently not supported by Qt. The supported formats
  are determined at run time, see \ref GetFileTypeMask.
*/  
int CCamStreamApp::GetNumberOfFileTypes() const
{
   return file_MAX;
}

/**
  \brief Return bitmak of available file formats.
   
  This function returns a bitmask of available formats. Every enumerated
  fileformat is represented by its corresponding bit (use 1 << \e n to
  mask out a bit). The number of fileformats is thus limited to 32.

 */
int CCamStreamApp::GetFileTypeMask() const
{
   return snap_bitmask;
}

/**
  \brief Get string with the proper filename extension.
  \param n One of \ref FileTypeEnum
  
  Return the proper filename extension for the given file format. For example,
  for JPEG files, it will return "jpg".
*/
QString CCamStreamApp::GetFileTypeExtension(int n) const
{
   if (n >= 0 && n < file_MAX) {
     return QString(FileTypes[n].extension);
   }
   return QString("xxx"); /* something we don't understand */
}

/**
  \brief Get string with format for saving files.

  Return proper handler string for current file format (which is different
  from the file extension). See \b QImage::save(...)
 */
QString CCamStreamApp::GetFileTypeFormatStr(int n) const
{
   if (n >= 0 && n < file_MAX) {
     return QString(FileTypes[n].format);
   }
   return QString("");
}

/**
  \brief Find enumeration belonging to file format
  \return A positive integer on a match, -1 when nothing was found
  
  This function returns a number from \ref FileTypeEnum that matches 
  the string given in \e format. See \ref GetFileTypeFormatStr
*/  
int CCamStreamApp::FormatStrToEnum(const QString &format) const
{
   int i;
   
   for (i = 0; i < file_MAX; i++) 
      if (format == FileTypes[i].format)
        return i;
   return -1; // No match
}

QString CCamStreamApp::FormatStrToExtension(const QString &format) const
{
   int i;
   
   for (i = 0; i < file_MAX; i++) 
      if (format == FileTypes[i].format)
        return QString(FileTypes[i].extension);
   return QString(""); // No match
}


/**
  \brief Find the options of a videodevice, matching by name or device node
  \param name The name from the device, e.g. "CPiA webcam"
  \param node The device nodename, e.g. /dev/video1
  \param create If TRUE, will create a new structure which is added to the internal tree
  \return a \ref SVideoOptions structure, or NULL if nothing was found and create was FALSE

  This function tries to find a matching \ref SVideoOptions struct for 
  a device. It first searches the list looking for the 'name' (which is 
  a symbolic name returned by the device), if that fails it uses the
  device nodename.
  
  By setting \b create to TRUE, a SVideoOptions structure will be created when
  it wasn't found in the current list, and will be appended to the 
  known configurations.
*/

struct SVideoOptions *CCamStreamApp::FindVideoOptions(const QString &name, const QString &node, bool create)
{
   struct SVideoOptions *opt;

   qDebug("Trying to find video options for %s:%s", (const char *)name, (const char *)node);
   opt = VOpts.first();
   while (opt != NULL) {
qDebug("searching %s", (const char *)opt->GetDeviceName());
      if (opt->GetDeviceName() == name)
        break;
      opt = VOpts.next();
   }
   // if opt == NULL, search again

   if (opt == NULL && create) {
     QDomElement ne = Configuration.Root.createElement("me");
     
qDebug("Creating new video options");
     opt = new SVideoOptions(Configuration.Root);
     opt->SetDeviceName(name);
     VOpts.append(opt);
     Configuration.VideoDevices.appendChild(opt->GetXML());
qDebug(Configuration.Root.toString());
   }
   return opt;
}


