
#include <qregexp.h>
#include <qfiledialog.h>

#include "log.h"
#include "utils.h"
#include "global.h"
#include "dvdmenu.h"
#include "qdvdauthor.h"
#include "exportsrt.h"
#include "buttonobject.h"
#include "xml_dvdauthor.h"
#include "exportdvdauthor.h"
#include "sourcefileentry.h"

namespace Export
{

DVDAuthor::DVDAuthor ( QDVDAuthor *pDVDAuthor )
{
  m_bInsertComments = true;
  m_pDVDAuthor = pDVDAuthor;
}

DVDAuthor::~DVDAuthor ()
{
  cleanSourceFileEntris ();
}

void DVDAuthor::cleanSourceFileEntris ()
{
  uint t;
  // This will clean up the newly created SourceFileEntries.
  // Note that the SourceFileInfos stored in it belong to the document and 
  // are not to be deleted.
  for (t=0;t<m_listNewEntries.count();t++) {
    m_listNewEntries[t]->listFileInfos.clear ();
    iLOG ("listNewEntryObj<%d>=<%p> is beeing deleted", t, m_listNewEntries[t] );
    delete m_listNewEntries[t];
  }
  m_listNewEntries.clear ();
}  

/**
 * export will open a QFileDialog for selecting the dvdauthor xml file and will export
 * the dvdauthor.xml file,
 *
 */
bool DVDAuthor::exportXml ()
{
  QString qsFileName = QFileDialog::getSaveFileName (Global::qsCurrentPath, QObject::tr ("Xml files (*.xml *.XML)"));
  
  return createXml ( qsFileName );
}

bool DVDAuthor::createXml ()
{
  Utils theUtils;
  QString qsFileName = theUtils.getTempFile(QString (DVDAUTHOR_XML));
  return createXml ( qsFileName );
}

////////////////////////////////////////////////////////////////////////////////
// dvdauthort xml file creation:
//
// getMenu () -> getButton() -+-> getVideo ()
//                            +-> getAudio ()
//                            +-> getMenu  ()
//
// VMGM
//	menu entry="root"
//	<pre>if ( g2 gt 0 ) {
//	if ( g2 eq 1 ) jump titleset 1 menu entry root;
//	if ( g2 eq 2 ) jump titleset 2 menu entry root;
//	if ( g2 eq 3 ) jump titleset 3 menu entry root;
//	jump title 1; <!-- nothig there, just start the first title // -->
//	}</pre>
//
// TITLESET 1
//	menu entry="root"
//	<pre>if ( g3 gt 0 ) {
//	if ( g3 eq 0 ) jump title 1 chapter 2;
//	if ( g3 eq 1 ) jump title 1 chapter 5;
//	if ( g3 eq 2 ) jump title 2 chapter 1;
//	jump title 1; <!-- nothig there, just start the first title // -->
//	}</pre>
//
// TITLESET 2
//	menu entry="root"
//	<pre>if ( g3 gt 0 ) {
//	if ( g3 eq 0 ) jump title 1;
//	if ( g3 eq 1 ) jump title 2 chapter 8;
//	if ( g3 eq 2 ) jump title 3 chapter 4;
//	jump title 1; <!-- nothig there, just start the first title // -->
//	}</pre>
//	
//0)
// pre processing the SourceFileEntries:
//	if SourceFileEntry->iTileset is set, then assign all SourceFileInfos the same UNLESS the SourceFileInfo has its own iTitlset set.
//      if SourceFileInfos in SourceFIleEntry differ in format, auto pu into diff titleset.
//
//1) create listExportTitlsets
// run through all menus (incl VMGM)
// pMenu->getButtonList()
//	listButtons : 
//	-	extract associated movies
//	-	check if SourceFileInfo has titleset set. (if SourceFIleEntry, then all SourceFileInfos in same titleset).
//	-	run through listExportTitleset, compare qsFormat (if no Format is avai, create new titleset for each entry)
//

bool DVDAuthor::createXml ( QString &qsFileName )
{
  m_bInsertComments = false;
  // First step is to obtaiin the menu list (Incl VMGMenu ...).
  m_listOfAllMenus = m_pDVDAuthor->getSubMenus ();
  if ( ( m_listOfAllMenus.count() == 0 ) && 
       ( m_pDVDAuthor->getVMGMenu()->isEmpty () ) )
    return createNoMenuDVD ( qsFileName );

  m_listOfAllMenus.prepend ( m_pDVDAuthor->getVMGMenu() );
  
  populateListOfTitlesets   (            );
  stowMenusAway             (            );
  createJumpTable           (            );
  return createDVDAuthorXml ( qsFileName );
}

bool DVDAuthor::populateListOfTitlesets ()
{
  bool             bFound = false;
  uint             iMenu, t, i;
  DVDMenu         *pMenu;
  QString          qsFormat;
  int              iTitleset;
  ExportTitleset  *pTitleset = NULL;
  SourceFileInfo  *pInfo;
  SourceFileEntry *pEntry;
  QValueList<SourceFileEntry *> listTempButtonMovies, listButtonMovies;

  for (iMenu=0;iMenu<m_listOfAllMenus.count();iMenu++)  {
    pMenu = m_listOfAllMenus[iMenu];
    //    qsFormat = QString ("%1x%2x%3").arg(pMenu->getInterface()->iWidth).arg(pMenu->getInterface()->iHeight).arg(pMenu->fps());
    //    listButtonMovies = pMenu->getSourceFileInfos ();
    listTempButtonMovies = pMenu->getSourceFileEntries ();
    // Next we want to make sure we do not modify the orig SourceFileEntries ...
    for (t=0;t<listTempButtonMovies.count();t++) {
      pEntry = listTempButtonMovies[t]->flatClone ();
      listButtonMovies.append ( pEntry );
      m_listNewEntries.append ( pEntry );
    }
    // clean Up will ensure that each SourceFileEntry only holds same format movies (WidthxHeightxFPS)
    cleanUpSourceFileEntries ( listButtonMovies );

    for (t=0;t<listButtonMovies.count();t++)  {
      pEntry = listButtonMovies[t];
      if ( pEntry->listFileInfos.count() < 1 )
	continue;

      bFound = false;
      // At this point we have already ensured that all videos in a SourceFileEntry
      // are of the same width/height/FPS
      pInfo = listButtonMovies[t]->listFileInfos[0];
      qsFormat = createFormatString ( pInfo, true );

      if ( pInfo->iTitleset > 0 )  {  // user set titleset number.
	pTitleset = addToTitleset ( pEntry, pInfo->iTitleset, true );
	bFound = true;
      }
      else {	// Next we check if we can find a titleset with the same format.
	for ( i=0;i<m_listTitlesets.count();i++)  {
	  pTitleset = m_listTitlesets[i];
          // Note: If the user assigns Titleset numbers then we should not add any additional videos to this titleset
	  if ( ( pTitleset->qsFormat == qsFormat ) && ( ! pTitleset->bSetByUser ) ) {
	    pTitleset = addToTitleset ( pEntry, pTitleset->iTitlesetNumber );
	    bFound = true;
	    break; // exit the loop
	  }
	}
      }
      // check if we could handle this movie yet.
      if ( ! bFound )  {
	iTitleset = getFirstEmptyTitleset ();
	pTitleset = addToTitleset ( pEntry, iTitleset );
      }
      if ( ! pTitleset ) {
	fprintf (stderr, "Error, Could not add titleset for menu <%s> with format:<%s>\n", pMenu->name ().ascii(), qsFormat.ascii());
	return false; //ERROR ..
      }
    }

    //2)  // At this point we have all movies tucked away in m_listTitlesets ...
          // In the second step we handle the button action and generate the JumpTable entries
	
    // Determine JumpTable per titleset
    // also set buttonAction

    //     { g3=iJumpIndex; jump menu 2; } // entry "JumpTable"; }
    // OR  { g3=iJumpIndex; g2=iTargetTitleset; jump VMGM menu 2; } // entry "JumpTable"; }
    // OR  { jump title iTitle chapter iChapter; }
    //	Note : g4 = current Titleset (origin of call )
    //	Note : g1 = current Button
  }
  return true;
}

bool DVDAuthor::stowMenusAway ()
{
  uint            iMenu, t;
  bool            bFound;
  QString         qsFormat;
  DVDMenu        *pMenu;
  ExportTitleset *pTitleset;
  // second we find a nice titleset for the menus
  for (iMenu=0;iMenu<m_listOfAllMenus.count();iMenu++)  {
    // The titleset should be 
    // a) the same Format
    // b) have a movie which is referenced from the menu
    ////////////////////////////////////////////////////
    pMenu = m_listOfAllMenus[iMenu];
    qsFormat = QString ("%1x%2x%3").arg(pMenu->getInterface()->iWidth).arg(pMenu->getInterface()->iHeight).arg(pMenu->fps());
    if ( pMenu->name() == VMGM_LABEL ) {
      // A little special treatment for VMGM
      pTitleset = new ExportTitleset;
      pTitleset->iTitlesetNumber = 0;
      pTitleset->qsFormat = qsFormat;
      pTitleset->bSetByUser = true;
      pTitleset->listMenus.append ( pMenu );
      m_listTitlesets.append ( pTitleset );
      continue;
    }
    bFound = false;
    pTitleset = NULL;
    for ( t=0;t<m_listTitlesets.count();t++) {
      pTitleset = m_listTitlesets[t];
      // only one Menu in VMGM ... (you ask why. Why not ?)
      if ( pTitleset->iTitlesetNumber == 0 )
	continue;
      if ( qsFormat == pTitleset->qsFormat ) {
	pTitleset->listMenus.append ( pMenu );
	bFound = true;
	break; // out of the inner loop
      }
    }
    if ( ! bFound ) {
      // this line will create a new titleset, cause it aint gonna find it in the list.
      pTitleset = findTitleset ( pMenu );
    }
    if ( ! pTitleset )
      fprintf (stderr, "Error, Could not create titleset for menu <%s> with format:<%s>\n", pMenu->name ().ascii(), qsFormat.ascii());
  }
  return true;
}

bool DVDAuthor::createJumpTable ( )
{
  ExportTitleset *pMenuTitleset;
  ButtonObject   *pButton;
  JumpTable      *pJumpTableEntry;
  QString         qsAction, qsCurrentButton;
  DVDMenu        *pMenu;
  uint            iMenu, t;
  SourceFileInfo *pInfo;
  ExportTitleset *pTitleset;
  QValueList<ButtonObject *>listButtons;

  // This first run is to ensure all VOBs that are connected to a buttonObject have at least one chapter.
  // the function ButtonObject::chapter will add any missing "00:00:00.000" chapter marker in that case.
  for (iMenu=0;iMenu<m_listOfAllMenus.count();iMenu++)  {
    listButtons = m_listOfAllMenus[iMenu]->getButtons ();
    for (t=0;t<listButtons.count();t++)
      listButtons[t]->chapter ( false );
  }

  // third loop through the menus, in case the titlesets have moved ...
  for (iMenu=0;iMenu<m_listOfAllMenus.count();iMenu++)  {
    pMenu = m_listOfAllMenus[iMenu];
    listButtons = pMenu->getButtons ();
    // find the titleset which is associated with this menu
    pMenuTitleset = findTitleset ( pMenu );
    // Next create the Jump tables for all titlesets.
    for (t=0;t<listButtons.count();t++)  {
      // Create JumpTableEntry
      pButton = listButtons[t];
      pInfo   = pButton->sourceFileInfo ();
      pJumpTableEntry = new JumpTable;
      pTitleset = NULL;
      // store the active button number
      qsCurrentButton = QString ( "g4=%1; " ).arg ( ( t + 1 ) * 1024 );
      qsAction = QString ( " { %1%1 " ).arg( qsCurrentButton ).arg ( pButton->preAction ( ) );
      getButtonNumberFromName ( listButtons, qsAction );

      if ( pInfo ) {  // button to a Video
	pTitleset = findTitleset ( pInfo );
	pJumpTableEntry->iJumpIndex = pTitleset->listJumpTable.count()+1;
	pJumpTableEntry->iTitle     = pTitleset->findTitle ( pInfo );
	pJumpTableEntry->iChapter   = pButton->chapter ( false ) + pTitleset->getTotalChapters ( pInfo );
	
	// Add button and buttonAction ...
	if ( pMenuTitleset == pTitleset ) {
	  if ( pJumpTableEntry->iChapter == 0 )
	    qsAction += QString ("jump title %1; } ").arg (pJumpTableEntry->iTitle);
	  else
	    qsAction += QString ("jump title %1 chapter %2; } ").arg (pJumpTableEntry->iTitle).arg (pJumpTableEntry->iChapter);
	}
	else if ( pMenuTitleset->iTitlesetNumber == 0 )	  // VMGM
	  qsAction += QString ("g3=%1; jump titleset %2 menu entry root; } ").arg(pJumpTableEntry->iJumpIndex).arg (pTitleset->iTitlesetNumber);
	else  // if we are in the wrong titleset then we have to go through the VMGM ...
	  qsAction += QString ("g2=%1; g3=%2; jump vmgm menu entry title; } ").arg (pTitleset->iTitlesetNumber).arg(pJumpTableEntry->iJumpIndex);
      }
      else {  // button to a menu   VMGM: jump titleset 1 menu 2;
	qsAction = QString ( " { g4=0; %1 " ).arg ( pButton->preAction ( ) );
	pMenu = getMenuFromButton( pButton );
	if ( pMenu ) {
	  pTitleset = findTitleset ( pMenu );
	  pJumpTableEntry->iJumpIndex = pTitleset->listJumpTable.count()+1;
	  pJumpTableEntry->iMenu      = pTitleset->findMenu ( pMenu );
	  pJumpTableEntry->pMenu      = pMenu;
	  if ( pMenuTitleset == pTitleset ) {
	    if ( pJumpTableEntry->iMenu == 0 )
	      qsAction += QString ("g3=0; jump menu entry root; } ");
	    else
	      qsAction += QString ("jump menu %1; } ").arg(pJumpTableEntry->iMenu);
	  }
	  else if ( pMenuTitleset->iTitlesetNumber == 0 )  // VMGM
	    qsAction += QString ("g2=%1; g3=%2; jump titleset %3 menu %4; } ").
	      arg (pTitleset->iTitlesetNumber).arg(pJumpTableEntry->iJumpIndex).arg (pTitleset->iTitlesetNumber).arg(pJumpTableEntry->iMenu);
	  else
	    qsAction += QString ("g2=%1; g3=%2; jump vmgm menu entry title; } ").arg (pTitleset->iTitlesetNumber).arg(pJumpTableEntry->iJumpIndex);
	}
	else { // error, no menu / video found that fits the bill ...
	  delete pJumpTableEntry;
	  continue;
	}
      }

      if ( m_bInsertComments )
	qsAction += QString (" <!-- %1 // -->").arg(pButton->action());
      pJumpTableEntry->qsAction = qsAction;
      pJumpTableEntry->pButton  = pButton;
      
      pTitleset->listJumpTable.append ( pJumpTableEntry );
    }
  }
  // at this point we have all information readily available to create the xml - file ...

  return true;
}

bool DVDAuthor::createNoMenuDVD ( QString &qsFileName )
{
  // This function will create a dvdauthor XML file for a DVD without a menu.
  // I.e. you put the DVD in and it will play all videos, one after another.
  int t, iSourceFileCount;
  CXmlDVDAuthor xmlDVDAuthor;
  CXmlDVDAuthor::titleset_struct *pXMLTitleset;

  if ( Global::qsProjectPath.isEmpty () )
    xmlDVDAuthor.m_dvdauthor.dest = QString ("/tmp/");
  else
    xmlDVDAuthor.m_dvdauthor.dest = Global::qsProjectPath;

  pXMLTitleset = xmlDVDAuthor.m_dvdauthor.addTitleset();

  iSourceFileCount = m_pDVDAuthor->sourceFileCount ();
  for ( t=0;t<iSourceFileCount;t++)
    addToTitleset ( pXMLTitleset, m_pDVDAuthor->sourceFileEntry ( t ) );

  xmlDVDAuthor.writeXml ( qsFileName );
  return false;
}

bool DVDAuthor::createDVDAuthorXml ( QString &qsFileName )
{
  uint t, i;
  QString qsPre;
  CXmlDVDAuthor::titleset_struct *pXMLTitleset;
  CXmlDVDAuthor::menus_struct    *pXMLMenu;
  CXmlDVDAuthor::pgc_struct      *pPgc;
 
  // main DVDAuthorXML - object
  CXmlDVDAuthor::titles_struct *pTitle;
  CXmlDVDAuthor           xmlDvdauthor;
  QValueList<DVDMenu *>   listDVDMenus = m_pDVDAuthor->getSubMenus();
  CDVDMenuInterface      *pInterface   = m_pDVDAuthor->getVMGMenu ()->getInterface();
  ExportTitleset         *pExpTitleset;
  int                     iMaxTitlesetNumber;

  // First we create the main menu ...
  xmlDvdauthor.m_dvdauthor.dest = Global::qsProjectPath;
  xmlDvdauthor.m_dvdauthor.jumppad = QString ("yes");
  pXMLMenu = xmlDvdauthor.m_dvdauthor.vmgm.addMenus();

  addToVideo ( &pXMLMenu->video, pInterface->iWidth, pInterface->iHeight, pInterface->qsRatio );

  // First we create the JumpTable for the Main Menu VMGM
  // VMGM
  //	menu entry="root"
  //	<pre>if ( g2 gt 0 ) {
  //	if ( g2 eq 1 ) jump titleset 1 menu entry root;
  //	if ( g2 eq 2 ) jump titleset 2 menu entry root;
  //	if ( g2 eq 3 ) jump titleset 3 menu entry root;
  //	jump title 1; <!-- nothig there, just start the first title // -->
  //	}</pre>
  pPgc = pXMLMenu->addPgc ();
  // the following line will add the actual VMGMenu
  addToMenu ( pPgc, m_pDVDAuthor->getVMGMenu() );
  pPgc->entry = QString ("title");
  qsPre  = QString (" { if ( g4 gt 0 ) button=g4; \n");
  qsPre += QString ("\t   if ( g2 gt 0 )  { \n");
  for (t=0;t<m_listTitlesets.count();t++) {
    pExpTitleset = m_listTitlesets[t];
    // we need no jump from VMGM to VMGM
    if ( pExpTitleset->iTitlesetNumber == 0 )
      continue;
    qsPre += QString ("\t\tif ( g2 eq %1 ) jump titleset %2 menu entry root;\n").arg (pExpTitleset->iTitlesetNumber).arg (pExpTitleset->iTitlesetNumber);
  }

  qsPre   += QString ("\t\tjump title 1;");
  if ( m_bInsertComments )
    qsPre += QString (" <!-- nothing there, just start the first title // -->\n");
  qsPre   += QString ("\t\t}\n\t} ");
  pPgc->pre.value  = qsPre;
  pPgc->post.value = QString (" { g4=button; g2=0; jump vmgm menu 1; } ");

  // Next we should take care of all sub-menus ...
  iMaxTitlesetNumber = getHighestTitlesetNumber ();
  if ( iMaxTitlesetNumber < 0 )
       iMaxTitlesetNumber = 0;
  for (t=1;t<=(uint)iMaxTitlesetNumber;t++) {
    pExpTitleset = findTitleset ( t );
    pXMLTitleset = xmlDvdauthor.m_dvdauthor.addTitleset();
    // check if we found an empty titleset.
    if ( pExpTitleset ) {
      
      addToMenu ( pXMLTitleset, pExpTitleset );
      
      // Next we insert all Menus ...
      for (i=0;i<pExpTitleset->listMenus.count();i++)
	addToMenu (pXMLTitleset, pExpTitleset->listMenus[i]);
      
      // Next we insert all SourceFileInfos ...
      for (i=0;i<pExpTitleset->listSourceFileEntries.count();i++ )
	addToTitleset (pXMLTitleset, pExpTitleset->listSourceFileEntries[i]);
    }

    // Each Titleset ought to have at least one title with one pgc
    if ( ! pXMLTitleset->ppArrayTitles ) {
      pTitle = pXMLTitleset->addTitles ();
      pTitle->addPgc();
    }

    // Found out the hard way that a Titleset also ought to possess at least one menu - pgc - entry.
    if ( ! pXMLTitleset->ppArrayMenus )  {
      pXMLMenu = pXMLTitleset->addMenus ( );
      pXMLMenu->addPgc ( );
    }
  }

  xmlDvdauthor.writeXml( qsFileName );

  return true;
}

ExportTitleset *DVDAuthor::addToTitleset (SourceFileEntry *pEntry, int iTitleset, bool bSetByUser /* = false */ )
{
  uint t, i;
  bool bFound = false;
  ExportTitleset  *pTitleset   = NULL;
  SourceFileEntry *pTSEntry    = NULL;
  SourceFileInfo  *pFormatInfo = pEntry->listFileInfos[0];
  //  QString qsFormat = QString ("%1x%2").arg(pFormatInfo->qsResolution).arg(pFormatInfo->qsRatio);
  QString qsFormat = createFormatString ( pFormatInfo, true );

  iLOG ("pEntry=<%p> titleset=%d", pEntry, iTitleset );
  // bSetByUser flag will try to force the titlesetNumber
  for (t=0;t<m_listTitlesets.count();t++)  {
    pTitleset = m_listTitlesets[t];
    if ( pTitleset->iTitlesetNumber == iTitleset ) {
      // Check if the titleset is alraedy taken by the automatic assignement algol
      if ( bSetByUser && ! pTitleset->bSetByUser ) {
        // in which case we re-assign the current Tilesets titleset number.
        pTitleset->iTitlesetNumber = getFirstEmptyTitleset ( );
        // exit out of for-loop to create a new Titleset
        t=m_listTitlesets.count() + 1;
        bFound = false;
        continue;
      }
      // check if we already assigned this TitlesetNumber to some other video format that does not match
      if ( ( qsFormat != pTitleset->qsFormat ) )  {
	iTitleset = getFirstEmptyTitleset  ( );
	// exit out of for-loop to create a new Titleset
	t=m_listTitlesets.count() + 1;
	bFound = false;
	continue;
      }
      
      // Next run through the SourceFileInfo list and make sure the SourceFileInfo is not already present.
      for (i=0;i<pTitleset->listSourceFileEntries.count();i++)  {
	pTSEntry = pTitleset->listSourceFileEntries[i];
	//if ( pTitleset->listSourceFileEntries[i] == pEntry ) { // would generate muliple PGS for the same Entry if it comes from different menus.
	if ( pTSEntry->qsDisplayName == pEntry->qsDisplayName ) {
	  bFound = true;
	  break;
	}
      }
      if ( ! bFound )  {
	pTitleset->listSourceFileEntries.append ( pEntry );
	if ( ! pTitleset->bSetByUser ) 
	  pTitleset->bSetByUser = bSetByUser;
      }
      bFound = true;
    }
    else  {
      // Next we check if this entry is in any other titleset and remove it
      for (i=0;i<pTitleset->listSourceFileEntries.count();i++)  {
	pTSEntry = pTitleset->listSourceFileEntries[i];
	//if ( pTitleset->listSourceFileEntries[i] == pEntry ) // se above
	if ( pTSEntry->qsDisplayName == pEntry->qsDisplayName )
	  pTitleset->listSourceFileEntries.remove ( pEntry );
      }
    }
  }
  // Finally, if we have not found the titlsetNumber in the list, we should create a new titleset
  if ( ! bFound )  {
    pTitleset = new ExportTitleset;
    pTitleset->iTitlesetNumber = iTitleset;
    pTitleset->listSourceFileEntries.append ( pEntry );
    pTitleset->qsFormat = qsFormat;
    pTitleset->bSetByUser = bSetByUser;
    m_listTitlesets.append ( pTitleset );
  }
  
  // NOTE: This algo can result in empty titlesets (no associated SourceFileInfos ) if the user defines the titlesetNumber
  //       fo a given SourceFileInfo / SourceFileEntry.	
  return pTitleset;
}

bool DVDAuthor::addToTitleset ( CXmlDVDAuthor::titleset_struct *pXMLTitleset, SourceFileEntry *pEntry )
{
  // This function creates the <titles> and the associated <video> tags.
  CXmlDVDAuthor::titles_struct *pTitle;
  SourceFileInfo *pInfo;
  TranscodeInterface *p;
  // Note: according to DVDAuthor spec only one Titles - tag exists. My bad when designing the class !
  iLOG ("pEntry=<%p> count=%d", pEntry,  pEntry->listFileInfos.count() );
  if ( ! pXMLTitleset->ppArrayTitles ) {
    pTitle = pXMLTitleset->addTitles ();
    if ( pEntry->listFileInfos.count() ) {
      pInfo = pEntry->listFileInfos[0];
      if ( pInfo ) {
	p = pInfo->pTranscodeInterface;
	if ( p )
	  addToVideo ( &pTitle->video, p->qsResolution, p->qsRatio );	  
	else
	  addToVideo ( &pTitle->video, pInfo->qsResolution, pInfo->qsRatio );
      }
    }
  }
  pTitle = pXMLTitleset->ppArrayTitles[0];
  return addToTitleset ( pTitle, pEntry );
}

bool DVDAuthor::addToTitleset ( CXmlDVDAuthor::titles_struct *pTitle, SourceFileEntry *pEntry )
{
  // This function creates the <titles> part of a titleset.
  CXmlDVDAuthor::pgc_struct *pPgc = NULL;
  ExportTitleset temp;
  // There are two ways to do this.

  // The first way is if none of the info's has and post or pre
  // command. All videos are stored in one title ( pgc )
  if ( ! temp.checkForPrePostCommands ( pEntry, true ) ) { // true == the fist pPgc can have a pre and the last is allowed to have a post command
    pPgc = pTitle->addPgc ( );
    return addToTitleset  ( pPgc, pEntry );
  }

  // The second way is to give each video a title. 
  // Note: according to DVD spec only 99 titles 
  // per titleset are allowed.
  uint i;
  for (i=0;i<pEntry->listFileInfos.count();i++) {
    pPgc = pTitle->addPgc ();
    addToTitleset ( pPgc, pEntry->listFileInfos[i] );
  }
  // just in case to ensure something is happening afterwards.
  if ( pPgc && pPgc->post.value.isEmpty () )
    pPgc->post.value = QString (" { g2 = 0; call vmgm menu entry title; } ");

  return true;
}

bool DVDAuthor::addToTitleset ( CXmlDVDAuthor::pgc_struct *pPgc, SourceFileInfo *pInfo )
{
  // This function assigns the Video files to the <titles> part of the TitleSet
  CXmlDVDAuthor::vob_struct *pVob;
  pPgc->pre.value  = pInfo->qsPreCommand;
  pPgc->post.value = pInfo->qsPostCommand;

  pVob = pPgc->addVob();
  pVob->file     = pInfo->getFileName ();
  pVob->chapters = pInfo->listChapters.join(QString(","));
  if ( pInfo->iPause == -1 )
    pVob->pause  = QString ("inf");
  else
    pVob->pause  = QString ("%1").arg (pInfo->iPause);

  return true;
}

QString DVDAuthor::jumpToSubMenu ( SourceFileEntry *pEntry )
{
  // This function will generate the jump command to the DVDMenu 
  // which is associated to the SourceFileEntry.
  int             iMenu;
  DVDMenu        *pDVDMenu;
  JumpTable      *pJumpTableEntry;
  ExportTitleset *pMenuTitleset, *pEntryTitleset;
  QString qsJump (" { g2 = 0; call vmgm menu entry title; } ");

  pDVDMenu   =  getMenuFromEntry (  pEntry  );
  if ( ! pDVDMenu )
    return qsJump;
  pEntryTitleset  = findTitleset (  pEntry  );
  if ( ! pEntryTitleset )
    return qsJump;
  pMenuTitleset   = findTitleset ( pDVDMenu );
  if ( ! pMenuTitleset )
    return qsJump;
  pJumpTableEntry = pMenuTitleset->findJumpTable ( pDVDMenu );
  if ( ! pJumpTableEntry )
    return qsJump;
    
  iMenu  = pMenuTitleset->findMenu ( pDVDMenu );
  if ( pMenuTitleset == pEntryTitleset ) {
    if ( pJumpTableEntry->iMenu == 0 )
      qsJump = QString (" { g3=0; jump menu entry root; } ");
    else
      qsJump = QString (" jump menu %1; ").arg ( iMenu );
    //qsJump = QString (" jump menu %1; ").arg ( pJumpTableEntry->iMenu );
  }
  else if ( pMenuTitleset->iTitlesetNumber != 0 )  // VMGM is the default
    qsJump = QString (" { g2=%1; g3=%2; call vmgm menu entry title; } ").
      arg ( pMenuTitleset->iTitlesetNumber ).arg( pJumpTableEntry->iJumpIndex );

  return qsJump;
}

bool DVDAuthor::addToTitleset ( CXmlDVDAuthor::pgc_struct *pPgc, SourceFileEntry *pEntry )
{
  uint i;
  for (i=0;i<pEntry->listFileInfos.count();i++) 
    addToTitleset ( pPgc, pEntry->listFileInfos[i] );
  
  pPgc->pre.value  = pEntry->qsPre;
  pPgc->post.value = pEntry->qsPost;
  if ( pEntry->qsPost.isEmpty ( ) )
    pPgc->post.value = jumpToSubMenu ( pEntry );
  //pPgc->post.value = QString (" { g2 = 0; call vmgm menu entry title; } ");
    
  if ( pEntry->iPause == -1 )
    pPgc->pause = QString ("inf");
  else
    pPgc->pause = QString ("%1").arg (pEntry->iPause);

  return true;
}

bool DVDAuthor::addToMenu ( CXmlDVDAuthor::titleset_struct *pXMLTitleset, ExportTitleset *pExpTitleset )
{
  CXmlDVDAuthor::menus_struct    *pXMLMenu;
  // TITLESET X
  //	menu entry="root"
  //	<pre>if ( g3 gt 0 ) {
  //	if ( g3 eq 0 ) jump title 1 chapter 2;
  //	if ( g3 eq 1 ) jump title 1 chapter 5;
  //	if ( g3 eq 2 ) jump title 2 chapter 1;
  //	jump title 1; <!-- nothig there, just start the first title // -->
  //	}</pre>
  if ( pExpTitleset->listJumpTable.count () > 0 ) {
    // Note there ought to be only one <menus> - tag.
    // my mistake for not understanding the dvdauthor-XML-spec
    if ( ! pXMLTitleset->ppArrayMenus )
      pXMLTitleset->addMenus ();
    pXMLMenu = pXMLTitleset->ppArrayMenus[0];
    
    return addToMenu ( pXMLMenu, pExpTitleset );
  } 
  return true;
}

bool DVDAuthor::addToMenu ( CXmlDVDAuthor::menus_struct *pXMLMenu, ExportTitleset *pExpTitleset )
{
  CXmlDVDAuthor::pgc_struct      *pPgc;
  pPgc = pXMLMenu->addPgc ();
  return addToMenu ( pPgc, pExpTitleset );
}

bool DVDAuthor::addToMenu ( CXmlDVDAuthor::pgc_struct *pPgc, ExportTitleset *pExpTitleset )
{
  uint i;
  QString qsPre;
 
  pPgc->entry=QString ("root");
  qsPre = QString (" {   if ( g3 gt 0 )  {\n");
  for ( i=0;i<pExpTitleset->listJumpTable.count();i++) {
    JumpTable *p = pExpTitleset->listJumpTable[i];
    if ( ! p ) // error but at least don't crash
      continue;
    if ( p->iMenu > -1 ) {
      // The following line confuses the xine virtual machine and rejected calling the menu.
      // qsPre += QString ("\t\tif ( g3 eq %1 ) { g3=0; jump menu %2; }\n").arg(p->iJumpIndex).arg(p->iMenu);
      qsPre += QString ("\t\tif ( g3 eq %1 ) jump menu %2; \n").arg(p->iJumpIndex).arg(p->iMenu);
    }
    else {
      qsPre += QString ("\t\tif ( g3 eq %1 ) { g3=0; jump title %2 ").arg(p->iJumpIndex).arg(p->iTitle);
      if ( p->iChapter < 1 )
	qsPre += QString ("; }\n");
      else
	qsPre += QString (" chapter %1; }\n").arg (p->iChapter);
    }
  }
  
  qsPre   += QString ("\t\tjump vmgm menu entry title;\n");
  if ( m_bInsertComments )
    qsPre += QString (" <!-- nothing there, just start the first title // -->\n");
  qsPre   += QString ("\t\t}\n\t} ");
  pPgc->pre.value  = qsPre;

  pPgc->post.value = QString (" { g2 = 0; jump vmgm menu entry title; } ");
  return true;
} 

bool DVDAuthor::addToMenu ( CXmlDVDAuthor::titleset_struct *pXMLTitleset, DVDMenu *pMenu )
{
  CXmlDVDAuthor::menus_struct    *pXMLMenu;
  //  <menus>
  //   <video format="ntsc" aspect="4:3" resolution="720x480" />
  //   <pgc entry="title" >
  //    <pre> g1=1; g2=1024; g3=1; </pre>
  //    <vob file="/tmp/09/Main Menu VMGM_menu.mpg" pause="inf" />
  //    <button name="01_Button_2" > { g3=2; jump titleset 1 menu entry root; } </button>
  //    <button name="02_Button_1" > { g3=5; jump titleset 1 menu entry root; } </button>
  //    <button name="03_Button_3" > { g3=10; jump titleset 1 menu entry root; } </button>
  //    <post> jump vmgm menu 1; </post>
  //   </pgc>
  //  </menus>
  // Note: the JumpTabel and the buttons of this menu better be in synch

  // The VMGMenu is already taken care of.
  if ( pMenu == m_pDVDAuthor->getVMGMenu () )
    return true;

  if ( ! pXMLTitleset->ppArrayMenus )
    pXMLTitleset->addMenus();
  pXMLMenu = pXMLTitleset->ppArrayMenus[0];
  return addToMenu ( pXMLMenu, pMenu );
}

bool DVDAuthor::addToMenu ( CXmlDVDAuthor::menus_struct *pXMLMenu, DVDMenu *pMenu )
{
  CDVDMenuInterface *pInterface = pMenu->getInterface();
  CXmlDVDAuthor::pgc_struct      *pPgc;
  addToVideo ( &pXMLMenu->video, pInterface->iWidth, pInterface->iHeight, pInterface->qsRatio );
  
  pPgc = pXMLMenu->addPgc();
  return addToMenu ( pPgc, pMenu );
}

bool DVDAuthor::addToMenu ( CXmlDVDAuthor::pgc_struct *pPgc, DVDMenu *pMenu )
{
  uint t;
  CXmlDVDAuthor::vob_struct      *pVob;
  CXmlDVDAuthor::button_struct   *pXMLButton;
  QValueList <ButtonObject *> listButtonsFromMenu = pMenu->getButtons ();
  ButtonObject *pButton;
  QString qsButtonName;
  JumpTable *pJumpTable;
  Utils theUtils;

  CDVDMenuInterface *pInterface   = pMenu->getInterface ( );
  for (t=0;t<pInterface->listIntroVobs.count ( );t++ ) {
    pVob = pPgc->addVob ( );
    pVob->file  = pInterface->listIntroVobs[t]->file;
    pVob->pause = QString ( "0" );
  }
  
  //  pPgc->entry = QString ("title");  
  //  pPgc->pre.value = QString (" { g1=1; g2=1024; g3=2; } ");
  pVob = pPgc->addVob ();
  pVob->file=QString ("%1_menu.mpg").arg(theUtils.getTempFile(pMenu->name()));
  // since version 0.0.9 we also support movieMenus, so we need to loop here
  // FIXME:
  // Only one problem yet unresolved is that the selection does reset after a loop.
  // This can be fixed through using e.g. the g-register of the virtual machine.
  for (t=0;t<pInterface->listExtroVobs.count ( );t++ ) {
    pVob = pPgc->addVob ( );
    pVob->file  = pInterface->listExtroVobs[t]->file;
    pVob->pause = QString ( "0" );
  }

  if (pMenu->isMovieMenu ())
    pVob->pause = QString ("0");
  else
    pVob->pause = QString ("inf");

  for ( t=0;t<listButtonsFromMenu.count();t++) {
    pButton = listButtonsFromMenu [ t ];
    qsButtonName = pButton->name ();
    qsButtonName.replace (" ", "_");
    pXMLButton = pPgc->addButton();
    pXMLButton->name.sprintf ("%02d_%s", t+1, qsButtonName.ascii());
    pJumpTable = findJumpTableEntry ( pButton );
    if ( ! pJumpTable ) { // In case I could not make heads or tails out of the action ...
      pXMLButton->value  = QString ("<!-- Could not find associated ButtonObject in JumpTables // -->\n");
      if ( pButton->action ( ).find ( STRING_SEPARATOR ) < 0 ) { // this indicates action was crreated through qdvdauthor. Internal error (missing source/menu) ! ignore !
	// In here indicates manual changes to the buttons - action tag. We should honor these.
	if ( ! pButton->preAction ( ).isEmpty ( ) ) //
	  pXMLButton->value += QString ( " { " ) + pButton->preAction ( ) + pButton->action ( ) + QString ( " } " );
	else
	  pXMLButton->value += pButton->action ( );
	getButtonNumberFromName ( listButtonsFromMenu, pXMLButton->value );
      }
    }
    else
      pXMLButton->value = pJumpTable->qsAction;
  }
  return true;
}

bool DVDAuthor::addToVideo ( CXmlDVDAuthor::video_struct *pVideo, QString &qsResolution, QString &qsAspect )
{
  QString qsSupportedAspect;
  // Currently dvdauthor only supports those two aspect ratios
  if ( qsAspect == "16:9" )
    qsSupportedAspect = "16:9";
  else if ( qsAspect == "4:3" )
    qsSupportedAspect = "4:3";
  QString qsRes = qsResolution;
  int iWidth, iHeight, iIdx = qsRes.find ( "x" );
  iWidth  = qsRes.left ( iIdx ).toInt();
  iHeight = qsRes.right( qsRes.length() - iIdx - 1 ).toInt();
  if ( ( iWidth < 1 ) || ( iHeight < 1 ) )
    return false;
  return addToVideo ( pVideo, iWidth, iHeight, qsSupportedAspect );
}

bool DVDAuthor::addToVideo ( CXmlDVDAuthor::video_struct *pXMLVideo, int iWidth, int iHeight, QString &qsAspect)
{
  int iFormat;
  // iFormat > 4 equals PAL, otherwise NTSC
  iFormat = m_pDVDAuthor->getVMGMenu()->getFormat (iWidth, iHeight);
  // The first thing we specify is the video settings ...
  if (iFormat > 4)
    pXMLVideo->format   = QString ("pal");
  else
    pXMLVideo->format   = QString ("ntsc");
  pXMLVideo->resolution = QString ("%1x%2").arg(iWidth).arg(iHeight);
  pXMLVideo->aspect     = qsAspect;
  return true;
}

ExportTitleset *DVDAuthor::findTitleset ( int iTitlesetNumber )
{
	uint t;
	for (t=0;t<m_listTitlesets.count ();t++)  {
		if ( m_listTitlesets[t]->iTitlesetNumber == iTitlesetNumber )
			return m_listTitlesets[t];
	}
	return NULL;
}

ExportTitleset *DVDAuthor::findTitleset ( SourceFileInfo *pInfo )
{
	uint t;
	for (t=0;t<m_listTitlesets.count ();t++)  {
		if ( m_listTitlesets[t]->findTitle ( pInfo ) != -1 )
			return m_listTitlesets[t];
	}
	return NULL;
}

ExportTitleset *DVDAuthor::findTitleset ( SourceFileEntry *pEntry )
{
	uint t;
	for (t=0;t<m_listTitlesets.count ();t++)  {
		if ( m_listTitlesets[t]->findTitle ( pEntry ) != -1 )
			return m_listTitlesets[t];
	}
	return NULL;
}

ExportTitleset *DVDAuthor::findTitleset ( DVDMenu *pMenu )
{
        uint t, i;
	QString            qsMenuFormat;
	ExportTitleset    *pTitleset;
	CDVDMenuInterface *pMenuInfo = pMenu->getInterface ();

	//	qsMenuFormat=QString ("%1x%2x%3").arg(pMenuInfo->iWidth).arg(pMenuInfo->iHeight).arg(pMenu->fps());
	//	for (t=0;t<m_listTitlesets.count ();t++)  {
	//	  if ( ( m_listTitlesets[t]->qsFormat == qsMenuFormat ) && 
	//	       ( m_listTitlesets[t]->iTitlesetNumber != 0 ) )
	//			return m_listTitlesets[t];
	//	}
	
	for (t=0;t<m_listTitlesets.count ();t++)  {
	  pTitleset = m_listTitlesets[t];
	  for (i=0;i<pTitleset->listMenus.count();i++) {
	    if ( pTitleset->listMenus[i] == pMenu )
	      return m_listTitlesets[t];
	  }
	}
	
	// We have to ensure that we return a titleset for this menu ...
	qsMenuFormat=QString ("%1x%2x%3").arg(pMenuInfo->iWidth).arg(pMenuInfo->iHeight).arg(pMenu->fps());
	pTitleset = new ExportTitleset;
	pTitleset->iTitlesetNumber = getFirstEmptyTitleset ();
	pTitleset->qsFormat = qsMenuFormat;
	pTitleset->bSetByUser = false;
	pTitleset->listMenus.append ( pMenu );
	m_listTitlesets.append ( pTitleset );
	
	return pTitleset;
}

ExportTitleset *DVDAuthor::findTitleset ( ButtonObject *pButton )
{
  DVDMenu *pMenu = getMenuFromButton ( pButton );
  if ( ! pMenu )
    return NULL;
  return findTitleset ( pMenu );
}

JumpTable *DVDAuthor::findJumpTableEntry ( ButtonObject *pButton )
{
  // This function will find the stored ButtonObject in the JumpTables
  // to obtain qsAction
  uint t, i;
  for (t=0;t<m_listTitlesets.count();t++) {
    for (i=0;i<m_listTitlesets[t]->listJumpTable.count();i++) {
      if ( pButton == m_listTitlesets[t]->listJumpTable[i]->pButton )
	return m_listTitlesets[t]->listJumpTable[i];
    }
  }
  return NULL;
}

int DVDAuthor::getHighestTitlesetNumber ()
{
	uint t;
	int iTitlesetNumber = -1;
	for (t=0;t<m_listTitlesets.count ();t++)  {
		if ( m_listTitlesets[t]->iTitlesetNumber > iTitlesetNumber )
			iTitlesetNumber = m_listTitlesets[t]->iTitlesetNumber;
	}
	return iTitlesetNumber;
}

int DVDAuthor::getFirstEmptyTitleset ()
{
  // This function runs through all m_listTitlesets and finds the lowest number that
  // either does not have any SourceFileInfos 
  // OR is not used (E.g. if titleset is not used but 4 is defined, then we use 3 ...
  uint t;
  int iTitlesetNumber = 1;
  bool bFound = true;
  
  while ( bFound )  {
    bFound = false;
    for (t=0;t<m_listTitlesets.count ();t++)  {
      if ( m_listTitlesets[t]->iTitlesetNumber == iTitlesetNumber ) {
	// Okay, this titleset number already exists, try the next one ...
	iTitlesetNumber++;
	bFound = true;
	break; // out of the for - loop
      }
    }
  }
  return iTitlesetNumber;
}
 
void DVDAuthor::getButtonNumberFromName ( QValueList <ButtonObject *> &listButtons, QString &qsAction )
{
  uint i;
  // replace button="Button Name" with button=2048
  qsAction.replace ( "\"", "&quot;" ); // this to make sure we catch either way " and &quot; ...
  QRegExp rx ( "button=&quot;.*&quot;" );
  if ( rx.search ( qsAction ) > -1 ) {
    QString qsButtonName = rx.cap ( 0 );
    qsButtonName.remove ( "button=" );
    qsButtonName.remove ( "&quot;" );
    for ( i=0; i<listButtons.count ( ); i++ ) {
      if ( listButtons[i]->name ( ) == qsButtonName ) {
	qsButtonName = QString ( "g4=%1; button=%1" ).arg ( ( i + 1 ) * 1024 ).arg ( ( i + 1 ) * 1024 );
	qsAction.replace ( rx, qsButtonName );
	return;
      }
    }
  }
}

DVDMenu *DVDAuthor::getMenuFromButton ( ButtonObject *pButton )
{
  uint               t;
  QString            qsButtonMenuName;
  QStringList        actionList;

  actionList = QStringList::split (QString (STRING_SEPARATOR), pButton->action() );
  if ( actionList.count() < 2 )
    return NULL;
  // A little special handling for VMGM menu jumps ...
  if ( actionList[1] == "vmgm" )
    qsButtonMenuName = VMGM_LABEL;
  else
    qsButtonMenuName = actionList[1];

  for (t=0;t<m_listOfAllMenus.count ();t++)  {
    if ( m_listOfAllMenus[t]->name() == qsButtonMenuName )
      return m_listOfAllMenus[t];
  }
  return NULL;
}

DVDMenu *DVDAuthor::getMenuFromEntry ( SourceFileEntry *pEntry )
{
  uint t, i, iSourceAssignedToCounter = 0;
  QValueList<SourceFileEntry *> listOfMenuEntries;
  DVDMenu *pDVDMenu, *pFoundDVDMenu = NULL;

  for (t=0;t<m_listOfAllMenus.count ( );t++)  {
    pDVDMenu = m_listOfAllMenus[t];
    listOfMenuEntries = pDVDMenu->getSourceFileEntries ( );
    for ( i=0; i<listOfMenuEntries.count ( ); i++ ) {
      if ( pEntry->qsDisplayName == listOfMenuEntries[i]->qsDisplayName ) {
	iSourceAssignedToCounter ++;
	pFoundDVDMenu = pDVDMenu;
      }
    }
  }
  // Only valid if the SourceFileEntry is assigned to only one menu.
  if ( iSourceAssignedToCounter == 1 )
    return pFoundDVDMenu;
  return NULL;
}

QString DVDAuthor::createFormatString ( SourceFileInfo *pInfo, bool bUseRatio )
{
  QString qsFormat;
  if ( ! pInfo )
    return qsFormat;

  TranscodeInterface *p = pInfo->pTranscodeInterface;

  if ( bUseRatio ) {
    qsFormat = QString ("%1x%2").arg ( pInfo->qsResolution ).arg( pInfo->qsRatio );
    if ( p )
      qsFormat = QString ("%1x%2").arg( p->qsResolution).arg( p->qsRatio );
  }
  else {
    qsFormat = QString ("%1x%2").arg ( pInfo->qsResolution ).arg( pInfo->qsFPS );
    if ( p )
      qsFormat = QString ("%1x%2").arg( p->qsResolution).arg( (uint)(p->fFrameRate) );
  }
  return qsFormat;
}

bool DVDAuthor::cleanUpSourceFileEntries ( QValueList<SourceFileEntry *> &listEntries )
{
  uint t, i, j;
  bool bFound;
  QString qsFormat1, qsFormat2;
  SourceFileEntry *pEntry, *pNewEntry;
  SourceFileInfo  *pInfo, *pInfo1, *pInfo2;

  //  cleanSourceFileEntris ();

  for (t=0;t<listEntries.count();t++) {
    pEntry = listEntries[t];
    pInfo  = pEntry->listFileInfos[0];
    if ( ! pInfo )
      continue;
    //    qsFormat1 = QString ("%1x%2").arg(pInfo->qsResolution).arg(pInfo->qsFPS);
    qsFormat1 = createFormatString ( pInfo );
    for (i=1;i<pEntry->listFileInfos.count();i++) {
      pInfo1 = pEntry->listFileInfos[i];
      //      qsFormat2 = QString ("%1x%2").arg(pInfo1->qsResolution).arg(pInfo1->qsFPS);
      qsFormat2 = createFormatString ( pInfo1 );
      bFound = false;
      // Verify same format.
      if ( qsFormat1 != qsFormat2 ) {
	// We will strip this from the orig SourceFileEntry and put it in a new
	// one. Note: We do not plug it in one of the originals.
	for (j=0;j<m_listNewEntries.count();j++) {
	  pInfo2 = m_listNewEntries[j]->listFileInfos[0];
	  if ( ! pInfo2 ) 
	    continue;
	  //	  qsFormat2 = QString ("%1x%2").arg(pInfo2->qsResolution).arg(pInfo2->qsFPS);
	  qsFormat2 = createFormatString ( pInfo2 );
	  if ( qsFormat1 == qsFormat2 ) {
	    pEntry->listFileInfos.remove ( pInfo1 );
	    m_listNewEntries[j]->listFileInfos.append ( pInfo1 );	    
	    bFound = true;
	    break; // exit out of the inner loop listNewEntries.
	  }
	}
	// In case we did not find a new SourceFileEntry of suitable format
	if ( ! bFound ) {
	  pNewEntry = new SourceFileEntry;
	  iLOG ("Just created SourceFileEntry <%p>", pNewEntry );
	  pEntry->listFileInfos.remove ( pInfo1 );
	  pNewEntry->listFileInfos.append ( pInfo1 );
	  m_listNewEntries.append (pNewEntry);
	}
      }
    }
  }
  // finally we add all new entries ...
  for (t=0;t<m_listNewEntries.count();t++)
    listEntries.append ( m_listNewEntries[t] );

  return true;
}

/////////////////////////////////////////////////////////////////
//
// SPUMUX class implementation
//
/////////////////////////////////////////////////////////////////
Spumux::Spumux ()
{
}

Spumux::~Spumux ()
{
}

bool Spumux::createXml (DVDMenu *pMenu)
{
	uint t, i;
	QString qsUp, qsDown, qsLeft, qsRight, qsButtonName;
	CXmlSpumux xmlSpumux;
	CXmlSpumux::spu_struct *pSpu = xmlSpumux.m_subpictures.stream.addSpu();
	CXmlSpumux::button_struct *pXmlButton;
	ButtonObject *pButton;
	Utils theUtil;
	QColor transparentColor = pMenu->getColor(0); // for highlighted layer ... (TRANSPARENT_COLOR);

	pSpu->start			= QString ("00:00:00.0");
	pSpu->end			= QString ("00:00:00.0");
	pSpu->force			= QString ("yes");		// force is required for menus.

	char s[] = "000000";
	sprintf (s, "%02x%02x%02x",
		transparentColor.red  (), 
		transparentColor.green(), 
		transparentColor.blue ());
	pSpu->transparent	= QString (s);

//	pSpu->autooutline	= QString ("infer");
//	pSpu->autoorder		= QString ("rows");
	pSpu->highlight		= theUtil.getTempFile(pMenu->name()) + QString("/") + QString(HIGHLIGHTED_NAME);
	pSpu->select		= theUtil.getTempFile(pMenu->name()) + QString("/") + QString(SELECTED_NAME);
		// Need to figure this one out ...
//	pSpu->outlinewidth	= 5;
	QValueList<ButtonObject *> listButtons;
	QValueList<QRect>          listButtonRect;
	QValueList<CXmlSpumux::button_struct *> listXmlButtons;
	listButtons    = pMenu->getButtons();
	// First thing is to ensure we have a 1:1 relationship between rect and ButtonObjects.
	for (t=0;t<listButtons.count();t++)
	  listButtonRect.append   ( listButtons[t]->boundingRect ( ) );
	correctOverlappingButtons ( listButtonRect, pMenu->getInterface()->iWidth, pMenu->getInterface()->iHeight );
	for (t=0;t<listButtons.count();t++)	{
		pXmlButton = pSpu->addButton();
		pXmlButton->x0 = listButtonRect[t].left   ( ); // boundingRect ( )
		pXmlButton->y0 = listButtonRect[t].top    ( );
		pXmlButton->x1 = listButtonRect[t].right  ( );
		pXmlButton->y1 = listButtonRect[t].bottom ( );
		//		pXmlButton->label=QString ("%1").arg(t+1);
		qsButtonName = listButtons[t]->name ();
		qsButtonName.replace (" ", "_");
		pXmlButton->label.sprintf ("%02d_%s", t+1, qsButtonName.ascii());
		listXmlButtons.append(pXmlButton);
	}
	// After we have generated the Xml Buttons and assigned them some nice names,
	// we should connect up/down/left/right.
	for (t=0;t<listButtons.count ();t++)	{
		pXmlButton = listXmlButtons[t];	// Ought to be same number as listButtons.count()
		pButton = listButtons[t];
		for (i=0;i<listButtons.count();i++)	{
			// Here we find the button label which fits the button. 
			// I.e. we associate the label in the xml file to the label in the ButtonObject
			qsUp    = pButton->next(NEXT_BUTTON_UP);
			qsDown  = pButton->next(NEXT_BUTTON_DOWN);
			qsRight = pButton->next(NEXT_BUTTON_RIGHT);
			qsLeft  = pButton->next(NEXT_BUTTON_LEFT);
			if ( (!qsUp.isEmpty())    && (!qsUp    != QString ("-- default --")) && 
			     (qsUp    == listButtons[i]->name()) ) {
			  qsButtonName = qsUp.replace (" ", "_");
			  pXmlButton->up.sprintf ("%02d_%s", i+1, qsButtonName.ascii());
			}
			if ( (!qsDown.isEmpty())  && (!qsDown  != QString ("-- default --")) && 
			     (qsDown  == listButtons[i]->name()) ) {
			  qsButtonName = qsDown.replace (" ", "_");
			  pXmlButton->down.sprintf ("%02d_%s", i+1, qsButtonName.ascii());
			}
			if ( (!qsLeft.isEmpty())  && (!qsLeft  != QString ("-- default --")) && 
			     (qsLeft  == listButtons[i]->name()) ) {
			  qsButtonName = qsLeft.replace (" ", "_");
			  pXmlButton->left.sprintf ("%02d_%s", i+1, qsButtonName.ascii());
			}
			if ( (!qsRight.isEmpty()) && (!qsRight != QString ("-- default --")) && 
			     (qsRight == listButtons[i]->name()) ) {
			  qsButtonName = qsRight.replace (" ", "_");
			  pXmlButton->right.sprintf ("%02d_%s", i+1, qsButtonName.ascii());
			}
		}
	}
	QString fileSpumux = theUtil.getTempFile(pMenu->name()) + QString ("/menu.xml");
	xmlSpumux.writeXml (fileSpumux);
	return true;
}

bool Spumux::correctOverlappingButtons ( QValueList<QRect> &listOfRect, int iWidth, int iHeight )
{
  uint   t, i;
  int    iDelta;
  QRect *pRect1, *pRect2, intersection;
  
  // None or only one button, return empty 
  if ( listOfRect.count() < 1 )
    return false;

  for (t=0;t < listOfRect.count();t++) {
    pRect1 = & listOfRect[t];
    // First we check out-of-bounds
    if ( pRect1->top    () < 0 )
         pRect1->setTop    ( 1 );
    if ( pRect1->left   () < 0 )
         pRect1->setLeft   ( 1 );
    if ( ( iWidth  > 1 ) && ( pRect1->right  () > iWidth ) )
         pRect1->setRight  ( iWidth  - 1 );
    if ( ( iHeight > 1 ) && ( pRect1->bottom () > iHeight ) )
         pRect1->setBottom ( iHeight - 1 );

    for (i=t+1;i < listOfRect.count ();i++) {
      pRect2  =  & listOfRect[i];

      if ( pRect1->intersects ( *pRect2 ) ) {
	intersection = pRect1->intersect ( *pRect2 );
	
	// here we determine if the intersection is more long than tall
	if ( intersection.width ( ) > intersection.height ( ) ) {
	  // we subtract the delta from the top / bottom of the intersection rectangles.
	  iDelta = (int) ( ( intersection.height ( ) + 1 ) / 2.0 );
	  // ensure that the button which is higher to be rect1
	  if ( pRect1->top ( ) > pRect2->top ( ) ) {
	    pRect1 = & listOfRect [ i ];
	    pRect2 = & listOfRect [ t ];
	  }
	  // Now we subtract the delta from button1's bounding Rect ...
	  pRect1->setBottom ( pRect1->bottom ( ) - iDelta );
	  pRect2->setTop    ( pRect2->top    ( ) + iDelta );
	}
	else {
	  // we subtract the delta from the left / right of the intersection rectangles.
	  iDelta = (int) ( ( intersection.width ( ) + 1 ) / 2.0 );
	  // ensure that the button which is closer to the left button1
	  if ( pRect1->left () > pRect2->left ( ) ) {
	    pRect1 = & listOfRect [ i ];
	    pRect2 = & listOfRect [ t ];
	  }
	  
	  pRect1->setRight ( pRect1->right ( ) - iDelta );
	  pRect2->setLeft  ( pRect2->left  ( ) + iDelta );
	}
      }
      // Ensure pRect1 is the button we expect it to be.
      pRect1 = & listOfRect [ t ];
    }
  }

  return true;
}


// The following function takes care of creating the spumux file for subtitles.
//<subpictures>
//   <stream>
//      <textsub filename="demo1.srt" characterset="ISO8859-1" 
//         fontsize="28.0" font="arial.ttf" horizontal-alignment="left" 
//         vertical-alignment="bottom" left-margin="60" right-margin="60" 
//         top-margin="20" bottom-margin="30" subtitle-fps="25" 
//         movie-fps="25" movie-width="720" movie-height="574" />
//   </stream>
//</subpictures>
///////////////////////////////////////////////////////////////////////////////////
#if 0 // first version ...
bool Spumux::createXml ( SourceFileInfo *pInfo )
{
  if ( ! pInfo )
    return false;

  Subtitles *pSubtitles = pInfo->pSubtitles;
  if ( ! pSubtitles )
    return true;
  // Okay at this point we have all the info we need
  Utils      theUtil;
  CXmlSpumux xmlSpumux;
  Export::Srt srtFile;
  QFileInfo fileInfo ( pInfo->qsFileName );
  QString   fileSpumux, fileSrt;
  CXmlSpumux::textsub_struct *pTextsub = xmlSpumux.m_subpictures.stream.addTextsub ();
  
  // next is to save the srt - file ...
  fileSrt = theUtil.getTempFile( fileInfo.baseName () ) + QString ("/subtitles.srt");
  srtFile.writeFile ( pSubtitles, fileSrt );

  pTextsub->filename             = fileSrt;
  pTextsub->characterset         ="UTF-8";
  pTextsub->fontsize             = pSubtitles->font.pixelSize ( );
  // note: a link has to exist in ~/.spumux/fontFamily to its actual location.
  pTextsub->font                 = pSubtitles->font.family    ( ); 
  pTextsub->subtitle_fps         = pInfo->qsFPS.toFloat ();
  pTextsub->movie_fps            = pInfo->qsFPS.toFloat ();
  pTextsub->movie_width          = theUtil.getWHFromResolution ( pInfo->qsResolution, true );
  pTextsub->movie_height         = theUtil.getWHFromResolution ( pInfo->qsResolution, false );
  // simple sanity check ...
  if ( pTextsub->movie_width < 350 )
    pTextsub->movie_width = 720;
  if ( pTextsub->movie_height < 240 ) {
    pTextsub->movie_height = 480;
    if ( pInfo->qsFormat == QString ( "PAL" ) )
      pTextsub->movie_height = 576;
  }
  pTextsub->horizontal_alignment   = "center"; // "left" - "right"
  pTextsub->vertical_alignment     = "center"; // "top" - "bottom"
  if ( pSubtitles->rect.width () > 5 ) {
    pTextsub->left_margin   = (int)((float)(pTextsub->movie_width - pSubtitles->rect.width   ( ) / 2.0 ) );
    pTextsub->right_margin  = pTextsub->left_margin;
  }
  if ( pSubtitles->rect.height () > 5 ) {
    pTextsub->top_margin    = (int)((float)(pTextsub->movie_height - pSubtitles->rect.height ( ) / 2.0 ) );
    pTextsub->bottom_margin = pTextsub->top_margin;
  }
  // and lastly we save the xml file.
  fileSpumux = theUtil.getTempFile( fileInfo.baseName () ) + QString ("/subtitles.xml");
  xmlSpumux.writeXml ( fileSpumux );
  return true;
}
#else
bool Spumux::createXml ( SourceFileInfo *pInfo )
{
  if ( ! pInfo )
    return false;

  Subtitles *pSubtitles = pInfo->pSubtitles;
  if ( ! pSubtitles )
    return true;

  // Okay at this point we have all the info we need
  int t, iWidth, iHeight;
  Utils       theUtil;
  CXmlSpumux  xmlSpumux;
  QString     thePath, fileSpumux, qsStart;
  Export::Srt srtFile;
  QFileInfo fileInfo ( pInfo->qsFileName );

  CXmlSpumux::spu_struct *pSpu;
  Subtitles::entry *pEntry;
  thePath = theUtil.getTempFile( fileInfo.baseName () ) + QString ("/sub_");

  for (t=0;t< (int)pSubtitles->listOfSubtitles.count (); t++) {
    pEntry = pSubtitles->listOfSubtitles[t];
    pSpu = xmlSpumux.m_subpictures.stream.addSpu ();
    qsStart           = pEntry->qsTimeStart;
    qsStart.remove ( ":"      );
    qsStart.replace( ".", "_" );

    pSpu->start       = pEntry->qsTimeStart;
    pSpu->end         = pEntry->qsTimeStop;
    // disturbs when having transparency completely opaque.
    //      pSpu->transparent = pSubtitles->subColors[0].name ();
    //      pSpu->transparent.remove ( "#" );

    pSpu->image       = QString ("%1%2.png").arg ( thePath ).arg( qsStart );
    pSpu->force       = "no";
    // Here we do some trickery .If the rect is NULL OR it is the same size as the Movie, then we do our own thingy here.
    if ( ( pSubtitles->rect.x() == 0 ) && ( pSubtitles->rect.y() == 0 ) ) {
      iWidth  = theUtil.getWHFromResolution ( pInfo->qsResolution, true  );
      iHeight = theUtil.getWHFromResolution ( pInfo->qsResolution, false );
      if ( ( ( pSubtitles->rect.width () == iWidth ) && ( pSubtitles->rect.height () == iHeight ) ) ||
	   ( ( pSubtitles->rect.width () == 0      ) && ( pSubtitles->rect.height () == 0       ) ) ) {
	pSpu->yoffset = iHeight - 45 - pEntry->rect.width ();
	pSpu->xoffset = (int) ( ( (float)iWidth - pEntry->rect.width () ) / 2.0 );
      }
      else {	
	pSpu->xoffset = 0;
	pSpu->yoffset = 0;
      }
    }
    else {
      pSpu->xoffset   = pSubtitles->rect.x ();
      pSpu->yoffset   = pSubtitles->rect.y ();
    }
  }
  // and lastly we save the xml file.
  fileSpumux = theUtil.getTempFile( fileInfo.baseName () ) + QString ("/subtitles.xml");
  xmlSpumux.writeXml ( fileSpumux );
  return true;
}
#endif
bool Spumux::createXml ( )
{
  return false;
}

JumpTable::JumpTable ()
{
  iJumpIndex = iTitle = iChapter = iMenu = -1;
  pButton = NULL;
  pMenu   = NULL;
}

ExportTitleset::ExportTitleset ()
{

}

ExportTitleset::~ExportTitleset ()
{
  uint t;
  for (t=0;t<listJumpTable.count();t++)
    delete listJumpTable[t];
}

int ExportTitleset::findTitle ( SourceFileInfo *pInfo )
{
  uint t, i, iTitleCount;

  iTitleCount = 0;
  for ( t=0; t<listSourceFileEntries.count ();t++)  {
    for (i=0;i<listSourceFileEntries[t]->listFileInfos.count();i++) {
      if ( listSourceFileEntries[t]->listFileInfos[i] == pInfo ) {
	// Okay we found the info - object

	// Note changed 20060922 if multiple SourceFileInfos in one Entry and a button jumps to not the first
	//if ( checkForPrePostCommands ( listSourceFileEntries[t] ) )

	if ( checkForPrePostCommands ( listSourceFileEntries[t], true ) )
	  iTitleCount += i+1;
	else
	  iTitleCount ++;
	return iTitleCount;
      }
    }
    // If we find post/pre commands we will generate a title per video object (info)
    if ( checkForPrePostCommands ( listSourceFileEntries[t] ) )
      iTitleCount += listSourceFileEntries[t]->listFileInfos.count ();
    else // otherwise the whole entry is only one title
      iTitleCount ++;
  }
  // error if we get here but to keep things somewhat working, we should return 1
  return -1;
}

int ExportTitleset::findTitle ( SourceFileEntry *pEntry )
{
  uint t, iTitleCount;

  iTitleCount = 0;
  for ( t=0; t<listSourceFileEntries.count ();t++)  {
    if ( listSourceFileEntries[t]->qsDisplayName == pEntry->qsDisplayName ) {
      iTitleCount ++;
      return iTitleCount;
    }

    // If we find post/pre commands we will generate a title per video object (info)
    if ( checkForPrePostCommands ( listSourceFileEntries[t] ) )
      iTitleCount += listSourceFileEntries[t]->listFileInfos.count ();
    else // otherwise the whole entry is only one title
      iTitleCount ++;
  }
  // error if we get here but to keep things somewhat working, we should return 1
  return -1;
}

int ExportTitleset::findMenu ( DVDMenu *pMenu )
{
  uint t;
  if ( pMenu->name() == VMGM_LABEL )
    return 1;

  for (t=0;t<listMenus.count();t++) {
    if (listMenus[t] == pMenu )
      return t+2;
  }
  return 1;
}

JumpTable *ExportTitleset::findJumpTable ( DVDMenu *pDVDMenu )
{
  uint t;
  if ( pDVDMenu ) {
    for ( t=0; t<listJumpTable.count ( ); t++ ) {
      if ( listJumpTable[t]->pMenu == pDVDMenu )
	return listJumpTable[t];
    }
  }
  return NULL;
}

// This function will simply add all chapters of all existing JumpTableEntries
int ExportTitleset::getTotalChapters ( SourceFileInfo *pSourceFileInfo )
{
  if ( ! pSourceFileInfo )
    return 0;
  
  uint t, i, iTemp, iChapters = 0;
  // Note: as long as the format is the same we do not create a new PGC per SourceFileEntry ...
  for ( t=0; t<listSourceFileEntries.count ();t++)  {
    // different handling if we find pre post commands
    //    if ( checkForPrePostCommands ( listSourceFileEntries[t] ) )
      iChapters = 0;

    for (i=0;i<listSourceFileEntries[t]->listFileInfos.count();i++) {
      if ( listSourceFileEntries[t]->listFileInfos[i] == pSourceFileInfo ) {
	// if we find the info-object in a entry which has pre/post commands
	// then create a pgc per info, thus we return 0.

	// Note changed 20060922 if multiple SourceFileInfos in one Entry and a button jumps to not the first
	//if ( checkForPrePostCommands ( listSourceFileEntries[t] ) )

	if ( checkForPrePostCommands ( listSourceFileEntries[t], true ) )
	  return 0;
	else
	  return iChapters;
      }
      iTemp = listSourceFileEntries[t]->listFileInfos[i]->listChapters.count();
      // if no chapters are defined, then we'll have to add one to stay in sync.
      //iTemp      = ( iTemp < 1 ) ? 1 : iTemp;
      iChapters +=   iTemp;
    }
  }

  return 0;
}

bool ExportTitleset::checkForPrePostCommands ( SourceFileEntry *pEntry, bool bIgnoreFirstLast )
{
  uint i;
  // bIgnoreFirstLast will ignore the pre for the first SourceFileInfo
  // and the post command for the last SourceFileInfo.

  // if we ignore first pre and last post AND there is only one SourceFileInfo in the list then ...
  if ( ( bIgnoreFirstLast ) && ( pEntry->listFileInfos.count() < 1 ) )
    return false;
  
  uint iCount = pEntry->listFileInfos.count();
  for (i=0;i<iCount;i++) {
    if ( bIgnoreFirstLast )  {
      // check first SourceFileInfo
      if ( ( i == 0 ) && ( ! pEntry->listFileInfos[i]->qsPostCommand.isEmpty () ) )
	return true;
      // check last SourceFileInfo
      else if ( ( i == iCount - 1 ) && ( ! pEntry->listFileInfos[i]->qsPreCommand.isEmpty  () ) )
	return true;
      else if ( ( i != 0 ) && ( i != iCount-1 ) && (
		( ! pEntry->listFileInfos[i]->qsPreCommand.isEmpty  () ) || 
		( ! pEntry->listFileInfos[i]->qsPostCommand.isEmpty () ) ) )
	return true;
    } // if first and last are not to be ignored
    else if ( ( ! pEntry->listFileInfos[i]->qsPreCommand.isEmpty  () ) || 
	      ( ! pEntry->listFileInfos[i]->qsPostCommand.isEmpty () ) )
      return true;
  }

  return false;
}


}; // End of namespace Export
