/////////////////////////////////////////////////////////////////////////////
// Name:        DVD.cpp
// Purpose:     The class to store a DVD Structure (Titles/Menus)
// Author:      Alex Thuering
// Created:	29.01.2003
// RCS-ID:      $Id: DVD.cpp,v 1.40 2008/03/16 08:08:48 ntalex Exp $
// Copyright:   (c) Alex Thuering
// Licence:     GPL
/////////////////////////////////////////////////////////////////////////////

#include "DVD.h"
#include "Config.h"
#include <wxSVGXML/svgxmlhelpr.h>
#include <wxVillaLib/utils.h>
#include <sys/stat.h>
#include <wxSVG/mediadec_ffmpeg.h>

#define DATA_FILE(fname) wxFindDataFile(_T("data") + wxString(wxFILE_SEP_PATH) + fname)

//////////////////////////////// TextSub ////////////////////////////////////

TextSub::TextSub(wxString filename)
{
  m_filename = filename;
  m_characterSet = wxT("ISO8859-1");
  m_font = wxT("arial.ttf");
  m_fontSize = 28;
  m_hAlignment = wxT("left"); 
  m_vAlignment = wxT("bottom");
  m_leftMargin = 60;
  m_rightMargin = 60;
  m_topMargin = 20;
  m_bottomMargin = 30;
  m_subtitleFps = 25;
  m_movieFps = 25;
  m_movieWidth = 720;
  m_movieHeight = 574;
}

wxSvgXmlNode* TextSub::GetXML(DVDFileType type)
{
  wxSvgXmlNode* textsubNode = new wxSvgXmlNode(wxSVGXML_ELEMENT_NODE, _T("textsub"));
  textsubNode->AddProperty(_T("filename"), m_filename);
  if (m_characterSet != wxT("ISO8859-1"))
    textsubNode->AddProperty(_T("characterset"), m_characterSet);
  if (m_font != wxT("arial.ttf")) // || type == DVDAUTHOR_XML
    textsubNode->AddProperty(_T("font"), m_font);
  if (m_fontSize != 28)
    textsubNode->AddProperty(_T("fontsize"), wxString::Format(wxT("%g"), m_fontSize));
  if (m_hAlignment != wxT("left"))
    textsubNode->AddProperty(_T("horizontal-alignment"), m_hAlignment);
  if (m_vAlignment != wxT("bottom"))
    textsubNode->AddProperty(_T("vertical-alignment"), m_vAlignment);
  if (m_leftMargin != 60)
    textsubNode->AddProperty(_T("left-margin"), wxString::Format(wxT("%d"), m_leftMargin));
  if (m_rightMargin != 60)
    textsubNode->AddProperty(_T("right-margin"), wxString::Format(wxT("%d"), m_rightMargin));
  if (m_topMargin != 20)
    textsubNode->AddProperty(_T("top-margin"), wxString::Format(wxT("%d"), m_topMargin));
  if (m_bottomMargin != 30)
    textsubNode->AddProperty(_T("bottom-margin"), wxString::Format(wxT("%d"), m_bottomMargin));
  if (m_subtitleFps != 25)
    textsubNode->AddProperty(_T("subtitle-fps"), wxString::Format(wxT("%g"), m_subtitleFps));
  if (m_movieFps != 25)
    textsubNode->AddProperty(_T("movie-fps"), wxString::Format(wxT("%g"), m_movieFps));
  if (m_movieWidth != 720)
    textsubNode->AddProperty(_T("movie-width"), wxString::Format(wxT("%d"), m_movieWidth));
  if (m_movieHeight != 574)
    textsubNode->AddProperty(_T("movie-height"), wxString::Format(wxT("%d"), m_movieHeight));
  return textsubNode;
}

bool TextSub::PutXML(wxSvgXmlNode* node)
{
  wxString val;
  long lval;
  double dval;
  
  if (!node->GetPropVal(wxT("filename"), &m_filename))
    return false;
  
  node->GetPropVal(wxT("characterset"), &m_characterSet);
  node->GetPropVal(wxT("font"), &m_font);
  if (node->GetPropVal(wxT("fontsize"), &val) && val.ToDouble(&dval))
    m_fontSize = dval;
  node->GetPropVal(wxT("horizontal-alignment"), &m_hAlignment);
  node->GetPropVal(wxT("vertical-alignment"), &m_vAlignment);
  if (node->GetPropVal(wxT("left-margin"), &val) && val.ToLong(&lval))
    m_leftMargin = lval;
  if (node->GetPropVal(wxT("right-margin"), &val) && val.ToLong(&lval))
    m_rightMargin = lval;
  if (node->GetPropVal(wxT("top-margin"), &val) && val.ToLong(&lval))
    m_topMargin = lval;
  if (node->GetPropVal(wxT("bottom-margin"), &val) && val.ToLong(&lval))
    m_bottomMargin = lval;
  if (node->GetPropVal(wxT("subtitle-fps"), &val) && val.ToDouble(&dval))
    m_subtitleFps = dval;
  if (node->GetPropVal(wxT("movie-fps"), &val) && val.ToDouble(&dval))
    m_movieFps = dval;
  if (node->GetPropVal(wxT("movie-width"), &val) && val.ToLong(&lval))
    m_movieWidth = lval;
  if (node->GetPropVal(wxT("movie-height"), &val) && val.ToLong(&lval))
    m_movieHeight = lval;
  
  return true;
}

bool TextSub::SaveSpumux(wxString filename)
{
  wxSvgXmlDocument xml;
  wxSvgXmlNode* root = new wxSvgXmlNode(wxSVGXML_ELEMENT_NODE, wxT("subpictures"));
  wxSvgXmlNode* streamNode = new wxSvgXmlNode(wxSVGXML_ELEMENT_NODE, wxT("stream"));
  streamNode->AddChild(GetXML(DVDAUTHOR_XML));
  root->AddChild(streamNode);
  xml.SetRoot(root);
  return xml.Save(filename);
}

////////////////////////////////// Vob //////////////////////////////////////

Vob::~Vob()
{
  if (m_menu)
    delete m_menu;
  if (m_slideshow)
    delete m_slideshow;
  WX_CLEAR_ARRAY(m_subtitles)
}

long Vob::GetChapterTime(int chapter)
{
  long time = 0;
  if (chapter > 0)
  {
    wxString timeStr = m_chapters;
    while (timeStr.Find(wxT(',')) != wxNOT_FOUND && --chapter)
      timeStr = timeStr.AfterFirst(wxT(','));
    timeStr = timeStr.BeforeFirst(wxT(','));
    while (1)
    {
      time *= 60;
      if (timeStr.Find(wxT(':')) == wxNOT_FOUND)
      {
        double t = 0;
        if (timeStr.ToDouble(&t))
          time += (int) (t*1000);
        break;
      }
      else
      {
        long t = 0;
        if (timeStr.BeforeFirst(wxT(':')).ToLong(&t))
          time += t;
        timeStr = timeStr.AfterFirst(wxT(':'));
      }
    }
  }
  return time;
}

wxString Vob::GetVideoFrameUri(int chapter, long position)
{
  wxString uri = GetFilename().length() ? GetFilename() : GetVideoFilename();
  if (position == -1 && chapter > 0)
    position = GetChapterTime(chapter);
  if (position >= 0 && uri.length() > 0)
    uri += wxT('#') + wxString::Format(wxT("%d"), position);
  return uri;
}

void Vob::UpdateInfo()
{
  wxFfmpegMediaDecoder ffmpeg;
  bool ok = m_filename.length() && ffmpeg.Load(m_filename)
      || m_videoFilename.length() && ffmpeg.Load(m_videoFilename);
  m_duration = ok ? ffmpeg.GetDuration() : 0;
  m_videoSize = ok ? ffmpeg.GetVideoSize() : wxSize();
}

wxSvgXmlNode* Vob::GetXML(DVDFileType type, DVD* dvd)
{
  wxSvgXmlNode* node = new wxSvgXmlNode(wxSVGXML_ELEMENT_NODE, wxT("vob"));
  wxString fname = GetFilename();
  if (type == DVDAUTHOR_XML && GetTmpFilename().length())
    fname = GetTmpFilename();
  if (fname.length())
    node->AddProperty(wxT("file"), fname);
  if (type == DVDSTYLER_XML)
  {
	if (GetVideoFilename().length())
	  XmlWriteValue(node, wxT("video"), GetVideoFilename());
    for (unsigned int i=0; i<GetAudioFilenames().Count(); i++)
    {
      wxSvgXmlNode* audioNode = new wxSvgXmlNode(wxSVGXML_ELEMENT_NODE, wxT("audio"));
      audioNode->AddChild(new wxSvgXmlNode(wxSVGXML_TEXT_NODE, wxEmptyString,
        GetAudioFilenames()[i]));
      node->AddChild(audioNode);
    }
    for (unsigned int i=0; i<GetSubtitles().Count(); i++)
      node->AddChild(GetSubtitles()[i]->GetXML(type));
  }
	  
  if (GetPause() != 0)
  {
	wxString pause = wxT("inf");
	if (GetPause()>0)
	  pause = wxString::Format(wxT("%d"), GetPause());
	node->AddProperty(wxT("pause"), pause);
  }
  
  if (GetChapters().length() && !GetSlideshow())
	node->AddProperty(wxT("chapters"), GetChapters());
  
  if (GetMenu() && type == DVDSTYLER_XML)
    node->AddChild(GetMenu()->GetXML(type));
  
  if (GetSlideshow())
  {
    if (type == DVDSTYLER_XML)
      node->AddChild(GetSlideshow()->GetXML(type));
    else if (type == DVDAUTHOR_XML)
    {
      wxString chapters;
      int t = 1;
      for (unsigned i=1; i<GetSlideshow()->Count()/5; i++)
      {
        t += GetSlideshow()->GetDuration()*5;
        int h = t/3600;
        int m = (t%3600)/60;
        int s = t%60;
        if (chapters.length())
          chapters += wxT(",");
        chapters += wxString::Format(wxT("%d:%2d:%2d.1"), h, m, s);
      }
      node->AddProperty(wxT("chapters"), chapters);
    }
  }
  
  return node;
}

bool Vob::PutXML(wxSvgXmlNode* node)
{
  wxString val;
  long lval;
  
  node->GetPropVal(wxT("file"), &m_filename);
  if (m_filename.length())
     UpdateInfo();
  wxSvgXmlNode* child = node->GetChildren();
  while (child)
  {
	if (child->GetName() == wxT("video"))
	  SetVideoFilename(child->GetChildren()->GetContent());
	else if (child->GetName() == wxT("audio"))
	  GetAudioFilenames().Add(child->GetChildren()->GetContent());
    else if (child->GetName() == wxT("textsub"))
    {
      TextSub* textsub = new TextSub;
      textsub->PutXML(child);
	  GetSubtitles().Add(textsub);
    }
    else if (child->GetName() == wxT("menu"))
    {
      m_menu = new Menu();
      if (!m_menu->PutXML(child))
        return false;
    }
    else if (child->GetName() == wxT("slideshow"))
      m_slideshow = new Slideshow(child);
	child = child->GetNext();
  }
  if (node->GetPropVal(wxT("pause"), &val))
  {
    if (val == wxT("inf"))
	  m_pause = -1;
    else if (val.ToLong(&lval))
      m_pause = int(lval);
  }
  node->GetPropVal(wxT("chapters"), &m_chapters);
  return true;
}

unsigned int Vob::GetFileSize(wxString filename)
{
  wxStructStat fInfo;
  if (wxStat(filename, &fInfo) != 0)
	return 0;
  return fInfo.st_size/1024;
}

int Vob::GetSize(bool generated)
{
  if (generated)
  {
	if (GetTmpFilename().length())
	  return GetFileSize(GetTmpFilename());
	else if (GetFilename().length())
	  return GetFileSize(GetFilename());
	return 0;
  }
  
  int size = 0;
  if (GetMenu())
	size += 250;
  else if (GetSlideshow())
	size += 150*GetSlideshow()->GetDuration()*GetSlideshow()->Count();
  else if (GetFilename().length())
	size += GetFileSize(GetFilename());
  else
  {
    if (GetVideoFilename().length())
      size += GetFileSize(GetVideoFilename());
    for (unsigned int i=0; i<GetAudioFilenames().Count(); i++)
      size += GetFileSize(GetAudioFilenames()[i]);
    for (unsigned int i=0; i<GetSubtitles().Count(); i++)
      size += GetFileSize(GetSubtitles()[i]->GetFilename());
  }
  return size;
}

Vob* Vob::CreateEmptyVob(VideoFormat videoFormat, AudioFormat audioFormat)
{
  wxString filename = wxT("empty_pal_mp2.mpg");
  if (videoFormat == vfNTSC) {
  	filename = audioFormat == afMP2 ?
		wxT("empty_ntsc_mp2.mpg") : wxT("empty_ntsc_ac3.mpg");
  } else if (audioFormat != afMP2) {
    filename = wxT("empty_pal_ac3.mpg");
  }
  return new Vob(DATA_FILE(filename));
}

////////////////////////////////// Pgc //////////////////////////////////////

Menu* Pgc::GetMenu()
{
  for (int i=0; i<(int)GetVobs().GetCount(); i++)
	if (GetVobs()[i]->GetMenu())
	  return GetVobs()[i]->GetMenu();
  return NULL;
}

Slideshow* Pgc::GetSlideshow()
{
  for (int i=0; i<(int)GetVobs().GetCount(); i++)
	if (GetVobs()[i]->GetSlideshow())
	  return GetVobs()[i]->GetSlideshow();
  return NULL;
}

wxSvgXmlNode* Pgc::GetXML(DVDFileType type, DVD* dvd, wxString nextTitle)
{
  wxSvgXmlNode* node = new wxSvgXmlNode(wxSVGXML_ELEMENT_NODE, wxT("pgc"));
  if (m_entry.length())
	node->AddProperty(wxT("entry"), m_entry);

  if (m_palette.length())
	node->AddProperty(wxT("palette"), m_palette);
  
  if (GetMenu() && type == DVDAUTHOR_XML)
	GetMenu()->GetXML(type, dvd->GetPlayAllRegister(), node);
  
  if (GetSlideshow() && type == DVDAUTHOR_XML)
    GetSlideshow()->GetXML(type, node);
  
  for (int i=0; i<(int)GetVobs().GetCount(); i++)
	node->AddChild(GetVobs()[i]->GetXML(type, dvd));
  
  if (GetPreCommands().length())
	XmlWriteValue(node, wxT("pre"), GetPreCommands());

  wxString postCommands = GetPostCommands();
  if (dvd->GetPlayAllRegister() != -1 && nextTitle.length()) {
  	wxString g = wxT("g") + wxString::Format(wxT("%d"),dvd->GetPlayAllRegister());
  	postCommands = wxT("if (") + g + wxT("==1) jump ") + nextTitle + wxT(";") + postCommands;
  }
  if (postCommands.length())
	XmlWriteValue(node, wxT("post"), postCommands);
  
  return node;
}

bool Pgc::PutXML(wxSvgXmlNode* node)
{
  WX_CLEAR_ARRAY(GetVobs());
  
  wxString val;
  node->GetPropVal(wxT("entry"), &m_entry);
  node->GetPropVal(wxT("palette"), &m_palette);
  
  wxSvgXmlNode* child = node->GetChildren();
  Menu* menu = NULL; // old
  
  while (child)
  {
    if (child->GetName() == wxT("spumux")) // get spunode (old)
    {
      menu = new Menu();
      menu->PutXML(child);
    }
    else if (child->GetName() == wxT("vob")) // get vob
    {
      Vob* vob;
      if (menu)
        vob = new Vob(menu);
      else
        vob = new Vob(wxT(""));
      menu = NULL;
      if (vob->PutXML(child))
        GetVobs().Add(vob);
      else
      {
        delete vob;
        wxLogError(_("Can't load vob"));
        return false;
      }
    }
    else if (child->GetName() == wxT("pre")) // get pre commands
      SetPreCommands(child->GetChildren()->GetContent());
    else if (child->GetName() == wxT("post")) // get post commands
      SetPostCommands(child->GetChildren()->GetContent());
    child = child->GetNext();
  }
  return true;
}

////////////////////////////// Audio/Video ///////////////////////////////////

wxArrayString Audio::GetFormatStrings(wxString def)
{
  wxArrayString ret;
  ret.Add(def);
  ret.Add(wxT("mp2"));
  ret.Add(wxT("ac3"));
  ret.Add(wxT("dts"));
  ret.Add(wxT("pcm"));
  return ret;
}

wxArrayString Audio::GetQuantStrings(wxString def)
{
  wxArrayString ret;
  ret.Add(def);
  ret.Add(wxT("16bps"));
  ret.Add(wxT("20bps"));
  ret.Add(wxT("24bps"));
  ret.Add(wxT("drc"));
  return ret;
}

wxArrayString Audio::GetDolbyStrings(wxString def)
{
  wxArrayString ret;
  ret.Add(def);
  ret.Add(wxT("surround"));
  return ret;
}

wxArrayString Audio::GetSamplerateStrings(wxString def)
{
  wxArrayString ret;
  ret.Add(def);
  ret.Add(wxT("48khz"));
  ret.Add(wxT("96khz"));
  return ret;
}

wxSvgXmlNode* Audio::GetXML(DVDFileType type)
{
  wxSvgXmlNode* node = new wxSvgXmlNode(wxSVGXML_ELEMENT_NODE, wxT("audio"));
  
  if (m_format.length())
	node->AddProperty(wxT("format"), m_format);
  if (m_channels >= 0)
	node->AddProperty(wxT("channels"), wxString::Format(wxT("%d"), m_channels));
  if (m_quant.length())
	node->AddProperty(wxT("quant"), m_quant);
  if (m_dolby.length())
	node->AddProperty(wxT("dolby"), m_dolby);
  if (m_samplerate.length())
	node->AddProperty(wxT("samplerate"), m_samplerate);
  if (m_lang.length())
	node->AddProperty(wxT("lang"), m_lang);
  
  if (node->GetProperties() == NULL)
  {
	delete node;
	node = NULL;
  }
  return node;
}

bool Audio::PutXML(wxSvgXmlNode* node)
{
  if (node == NULL || node->GetName() !=  wxT("audio"))
	return false;
  
  wxString val;
  long lval;
  node->GetPropVal(wxT("format"), &m_format);
  if (node->GetPropVal(wxT("channels"), &val) && val.ToLong(&lval))
	m_channels = int(lval);
  node->GetPropVal(wxT("quant"), &m_quant);
  node->GetPropVal(wxT("dolby"), &m_dolby);
  node->GetPropVal(wxT("samplerate"), &m_samplerate);
  node->GetPropVal(wxT("lang"), &m_lang);
  
  return true;
}

wxArrayString Video::GetFormatStrings(wxString def)
{
  wxArrayString ret;
  ret.Add(def);
  ret.Add(wxT("pal"));
  ret.Add(wxT("ntsc"));
  return ret;
}

wxArrayString Video::GetAspectStrings(wxString def)
{
  wxArrayString ret;
  ret.Add(def);
  ret.Add(wxT("4:3"));
  ret.Add(wxT("16:9"));
  return ret;
}

wxArrayString Video::GetWidescreenStrings(wxString def)
{
  wxArrayString ret;
  ret.Add(def);
  ret.Add(wxT("nopanscan"));
  ret.Add(wxT("noletterbox"));
  return ret;
}

wxSvgXmlNode* Video::GetXML(DVDFileType type)
{
  wxSvgXmlNode* node = new wxSvgXmlNode(wxSVGXML_ELEMENT_NODE, wxT("video"));
  
  if (m_format.length())
	node->AddProperty(wxT("format"), m_format);
  if (m_aspect.length())
	node->AddProperty(wxT("aspect"), m_aspect);
  if (m_resolution.length())
	node->AddProperty(wxT("resolution"), m_resolution);
  if (m_caption.length())
	node->AddProperty(wxT("caption"), m_caption);
  if (m_widescreen.length())
	node->AddProperty(wxT("widescreen"), m_widescreen);
  
  if (node->GetProperties() == NULL)
  {
	delete node;
	node = NULL;
  }
  return node;
}

bool Video::PutXML(wxSvgXmlNode* node)
{
  if (node == NULL || node->GetName() !=  wxT("video"))
	return false;
  
  wxString val;
  node->GetPropVal(wxT("format"), &m_format);
  node->GetPropVal(wxT("aspect"), &m_aspect);
  node->GetPropVal(wxT("resolution"), &m_resolution);
  node->GetPropVal(wxT("caption"), &m_caption);
  node->GetPropVal(wxT("widescreen"), &m_widescreen);
  
  return true;
}
 
////////////////////////////////// Menus //////////////////////////////////////

wxSvgXmlNode* Menus::GetXML(DVDFileType type, DVD* dvd)
{
  wxSvgXmlNode* node = new wxSvgXmlNode(wxSVGXML_ELEMENT_NODE, wxT("menus"));
  if (GetAudio().GetXML(type))
	node->AddChild(GetAudio().GetXML(type));
  if (GetVideo().GetXML(type))
	node->AddChild(GetVideo().GetXML(type));
  for (int i=0; i<(int)GetCount(); i++)
	node->AddChild((*this)[i]->GetXML(type, dvd, wxT("")));
  return node;
}

bool Menus::PutXML(wxSvgXmlNode* node)
{
  if (node == NULL || node->GetName() !=  wxT("menus"))
	return false;
  WX_CLEAR_ARRAY(*this);
  wxSvgXmlNode* child = node->GetChildren();
  while (child)
  {
	if (child->GetName() == wxT("audio"))
	  GetAudio().PutXML(child);
	else if (child->GetName() == wxT("video"))
	  GetVideo().PutXML(child);
	else if (child->GetName() == wxT("pgc"))
	{
	  Pgc* pgc = new Pgc;
	  if (pgc->PutXML(child))
		Add(pgc);
	  else
	  {
		delete pgc;
		wxLogError(_("Can't load pgc"));
		return false;
	  }
	}
	child = child->GetNext();
  }
  return true;
}

////////////////////////////////// Titles ////////////////////////////////////

wxSvgXmlNode* Titles::GetXML(DVDFileType type, DVD* dvd, int nextTitleset)
{
  wxSvgXmlNode* node = new wxSvgXmlNode(wxSVGXML_ELEMENT_NODE, wxT("titles"));
  if (GetAudio().GetXML(type))
	node->AddChild(GetAudio().GetXML(type));
  if (GetVideo().GetXML(type))
	node->AddChild(GetVideo().GetXML(type));
  for (int i=0; i<(int)GetCount(); i++) {
  	wxString nextTitle; 
  	if (i != (int)GetCount()-1)
  		nextTitle = wxString::Format(wxT("title %d"), i+2);
  	else if (nextTitleset != -1)
  		nextTitle = wxString::Format(wxT("titleset %d title 1"),nextTitleset);
	node->AddChild((*this)[i]->GetXML(type, dvd, nextTitle));
  }
  return node;
}

bool Titles::PutXML(wxSvgXmlNode* node)
{
  if (node == NULL || node->GetName() !=  wxT("titles"))
	return false;
  wxSvgXmlNode* child = node->GetChildren();
  while (child)
  {
	if (child->GetName() == wxT("audio"))
	  GetAudio().PutXML(child);
	else if (child->GetName() == wxT("video"))
	  GetVideo().PutXML(child);
	else if (child->GetName() == wxT("pgc"))
	{
	  Pgc* pgc = new Pgc;
	  if (pgc->PutXML(child))
		Add(pgc);
	  else
	  {
		delete pgc;
		wxLogError(_("Can't load pgc"));
		return false;
	  }
	}
	child = child->GetNext();
  }
  return true;
}

////////////////////////////////// Titleset //////////////////////////////////

wxSvgXmlNode* Titleset::GetXML(DVDFileType type, DVD* dvd, int nextTitleset)
{
  wxSvgXmlNode* node = new wxSvgXmlNode(wxSVGXML_ELEMENT_NODE, wxT("titleset"));
  if (m_menus.Count() || type != DVDAUTHOR_XML)
	node->AddChild(m_menus.GetXML(type, dvd));
  else if (dvd->IsEmptyMenuEnabled() &&	dvd->GetVmgm().Count())
  {
	// create empty menu with post command
	Menus menus;
	Pgc* pgc = new Pgc;
	pgc->SetPreCommands(wxT("jump vmgm menu;"));
	pgc->GetVobs().Add(Vob::CreateEmptyVob(dvd->GetVideoFormat(), dvd->GetAudioFormat()));
	menus.Add(pgc);
	node->AddChild(menus.GetXML(type, dvd));
  }
  node->AddChild(m_titles.GetXML(type, dvd, nextTitleset));
  return node;
}

bool Titleset::PutXML(wxSvgXmlNode* node)
{
  if (node == NULL || node->GetName() !=  wxT("titleset"))
	return false;
  wxSvgXmlNode* child = node->GetChildren();
  while (child)
  {
	if (child->GetName() == wxT("menus"))
	{
	  if (!GetMenus().PutXML(child))
	  {
		wxLogError(_("Can't load menus"));
		return false;
	  }
	}
	else if (child->GetName() == wxT("titles"))
	{
	  if (!GetTitles().PutXML(child))
	  {
		wxLogError(_("Can't load titles"));
		return false;
	  }
	}
	child = child->GetNext();
  }
  return true;
}

////////////////////////////////// VMGM //////////////////////////////////////

wxSvgXmlNode* Vmgm::GetXML(DVDFileType type, DVD* dvd)
{
  wxSvgXmlNode* node = new wxSvgXmlNode(wxSVGXML_ELEMENT_NODE, wxT("vmgm"));
  // add fgc
  if (GetFpc().length())
  	XmlWriteValue(node, wxT("fpc"), GetFpc());
  
  // add menus
  if (type == DVDSTYLER_XML || Count())
	node->AddChild(Menus::GetXML(type, dvd));
  else if (dvd->IsEmptyMenuEnabled()
      && dvd->GetTitlesets().Count() && dvd->GetTitlesets()[0]->GetMenus().Count())
  {
	// create empty vmgm with post command
	Menus menus;
	Pgc* pgc = new Pgc;
	pgc->SetPreCommands(wxT("jump titleset 1 menu;"));
    pgc->GetVobs().Add(Vob::CreateEmptyVob(dvd->GetVideoFormat(), dvd->GetAudioFormat()));
	menus.Add(pgc);
	node->AddChild(menus.GetXML(DVDAUTHOR_XML, dvd));
  }
  return node;
}

bool Vmgm::PutXML(wxSvgXmlNode* node)
{	
  wxSvgXmlNode* child = node->GetChildren();
  while (child)
  {
	if (child->GetName() == wxT("menus"))
	  Menus::PutXML(child);
	else if (child->GetName() == wxT("fpc"))
	  SetFpc(child->GetChildren()->GetContent());
	child = child->GetNext();
  }
  return true;
}

////////////////////////////////// DVD //////////////////////////////////////

DVD::DVD()
{
  m_name = s_config.GetDefVolumeName();
  m_jumppad = true;
  m_emptyMenu = true;
  m_playAllRegister = -1;
}

DVD::~DVD()
{
  WX_CLEAR_ARRAY(m_vmgm);
  WX_CLEAR_ARRAY(m_titlesets);
}

int DVD::AddTitleset()
{
  Titleset* titleset = new Titleset;
  GetTitlesets().Add(titleset);
  return GetTitlesets().Count();
}

int DVD::AddMenu(VideoFormat format, int tsi)
{
  Menu* menu = new Menu((VideoFormat)format);
  Vob* vob = new Vob(menu);
  vob->SetPause(-1);
  Pgc* pgc = new Pgc;
  pgc->GetVobs().Add(vob);
  PgcArray& pgcs = GetPgcArray(tsi, true);
  if (pgcs.GetCount() == 0) // set aspect ratio
  	pgcs.GetVideo().SetAspect(wxT("4:3"));
  pgcs.Add(pgc);
  return pgcs.Count()-1;
}

int DVD::AddPgc(int tsi, bool menu, Pgc* pgc)
{
  if (!pgc)
	pgc = new Pgc;
  PgcArray& pgcs = GetPgcArray(tsi, menu);
  pgcs.Add(pgc);
  return pgcs.Count()-1;
}

PgcArray& DVD::GetPgcArray(int tsi, bool menus)
{
  if (tsi>=0 && tsi<(int)GetTitlesets().Count())
  {
	Titleset* ts = GetTitlesets()[tsi];
	if (menus)
		return ts->GetMenus();
	else
		return ts->GetTitles();
  }
  return GetVmgm();
}

Vob* DVD::GetVob(int tsi, bool isMenu, int pgci, int vobi)
{
  PgcArray& pgcs = GetPgcArray(tsi, isMenu);
  if (pgci<0 || pgci>=(int)pgcs.Count())
	return NULL;
  if (vobi<0 || vobi>=(int)pgcs[pgci]->GetVobs().Count())
	return NULL;
  return pgcs[pgci]->GetVobs()[vobi];
}

Vob* DVD::GetMenuVob(int tsi, int pgci)
{
  PgcArray& pgcs = GetPgcArray(tsi, true);
  if (pgci<0 || pgci>=(int)pgcs.Count())
	return NULL;
  // find vob with menu
  for (int vobi = 0; vobi<(int)pgcs[pgci]->GetVobs().GetCount(); vobi++) {
  	if (pgcs[pgci]->GetVobs()[vobi]->GetMenu() != NULL)
		return pgcs[pgci]->GetVobs()[vobi];
  }
  return NULL;
}

Menu* DVD::GetMenu(int tsi, int pgci)
{
  Vob* vob = GetMenuVob(tsi, pgci);
  if (vob)
	return vob->GetMenu();
  return NULL;
}

bool DVD::Open(wxString fname)
{
  WX_CLEAR_ARRAY(GetTitlesets());
  wxSvgXmlDocument xml;
  if (!xml.Load(fname))
  {
	wxLogError(_("Can't load xml-file"));
	return false;
  }
  
  wxSvgXmlNode* root = xml.GetRoot();
  if (root == NULL || root->GetName() !=  wxT("dvdstyler"))
  {
	wxLogError(_("This is not a DVDStyler project file"));
	return false;
  }
  
  root->GetPropVal(wxT("name"), &m_name);
  
  wxString val;
  long lval;
  
  int format = 0;
  if (root->GetPropVal(wxT("format"), &val) && val.ToLong(&lval))
  	format = int(lval);
  if (root->GetPropVal(wxT("jumppad"), &val) && val.ToLong(&lval))
	m_jumppad = int(lval);
  if (root->GetPropVal(wxT("emptyMenu"), &val) && val.ToLong(&lval))
	m_emptyMenu = int(lval);
  if (root->GetPropVal(wxT("videoFormat"), &val) && val.ToLong(&lval))
	m_videoFormat = VideoFormat(lval + (format == 2 ? 1 : 0));
  if (root->GetPropVal(wxT("audioFormat"), &val) && val.ToLong(&lval))
	m_audioFormat = AudioFormat(lval + (format == 2 ? 1 : 0));
  
  wxSvgXmlNode* child = root->GetChildren();
  while (child)
  {
	if (child->GetName() ==  wxT("vmgm"))
	{
	  if (!GetVmgm().PutXML(child))
	  {
		wxLogError(_("Can't load vmgm menus"));
		return false;
	  }
	}
	else if (child->GetName() ==  wxT("titleset"))
	{
	  Titleset* titleset = new Titleset;
	  if (titleset->PutXML(child))
		GetTitlesets().Add(titleset);
	  else
	  {
		delete titleset;
		wxLogError(_("Can't load titleset"));
		return false;
	  }
	}
	child = child->GetNext();
  }
  return true;
}

bool DVD::Save(wxString fname)
{
  m_playAllRegister = -1;
  wxSvgXmlDocument xml;
  wxSvgXmlNode* root = new wxSvgXmlNode(wxSVGXML_ELEMENT_NODE, wxT("dvdstyler"));
  root->AddProperty(wxT("format"), wxT("3"));
  root->AddProperty(wxT("name"), GetName());
  root->AddProperty(wxT("jumppad"), m_jumppad ? wxT("1") : wxT("0"));
  root->AddProperty(wxT("emptyMenu"), m_emptyMenu ? wxT("1") : wxT("0"));
  root->AddProperty(wxT("videoFormat"), wxString::Format(wxT("%d"), m_videoFormat));
  root->AddProperty(wxT("audioFormat"), wxString::Format(wxT("%d"), m_audioFormat));
  root->AddChild(GetVmgm().GetXML(DVDSTYLER_XML, this));
  for (int i=0; i<(int)m_titlesets.GetCount(); i++)
	if (m_titlesets[i]->GetMenus().Count() || m_titlesets[i]->GetTitles().Count())
	  root->AddChild(m_titlesets[i]->GetXML(DVDSTYLER_XML, this));
  xml.SetRoot(root);
  return xml.Save(fname);
}

bool DVD::SaveDVDAuthor(wxString fname)
{
  // check if we need "play all"
  if (HasPlayAllButton())
  	m_playAllRegister = FindFreeRegister();
  // save config for dvdauthor
  wxSvgXmlDocument xml;
  wxSvgXmlNode* root = new wxSvgXmlNode(wxSVGXML_ELEMENT_NODE, wxT("dvdauthor"));
  root->AddProperty(wxT("jumppad"), m_jumppad ? wxT("1") : wxT("0"));
  root->AddChild(GetVmgm().GetXML(DVDAUTHOR_XML, this));
  for (int i=0; i<(int)m_titlesets.GetCount(); i++) {
  	int nextTitleset = i<(int)m_titlesets.GetCount()-1 && !m_titlesets[i+1]->IsEmpty() ? i+2 : -1;
	if (!m_titlesets[i]->IsEmpty())
	  root->AddChild(m_titlesets[i]->GetXML(DVDAUTHOR_XML, this, nextTitleset));
  }
  xml.SetRoot(root);
  return xml.Save(fname);
}

int DVD::GetSize(bool generated) {
	int size = 0;
	for (int tsi = -1; tsi<(int)GetTitlesets().Count(); tsi++) {
		for (int m=0; m<=1; m++) {
			bool menu = m == 0;
			if (tsi == -1 && !menu) // "titleset -1" contains only vmMenus
				break;
			PgcArray& pgcs = GetPgcArray(tsi, menu);
			for (int pgci = 0; pgci<(int)pgcs.Count(); pgci++) {
				Pgc* pgc = pgcs[pgci];
				for (int vobi = 0; vobi<(int)pgc->GetVobs().Count(); vobi++)
					size += pgc->GetVobs()[vobi]->GetSize(generated);
			}
		}
	}
	return size;
}

bool DVD::HasPlayAllButton() {
	for (int tsi = -1; tsi<(int)GetTitlesets().Count(); tsi++) {
		PgcArray& pgcs = GetPgcArray(tsi, true);
		for (int pgci = 0; pgci<(int)pgcs.Count(); pgci++) {
			Pgc* pgc = pgcs[pgci];
			for (int vobi = 0; vobi<(int)pgc->GetVobs().Count(); vobi++) {
				Vob* vob = pgc->GetVobs()[vobi];
				if (vob->GetMenu()) {
					Menu* menu = vob->GetMenu();
					for (int obji = 0; obji<(int)menu->GetObjectsCount(); obji++)
						if (menu->GetObject(obji)->IsPlayAll())
							return true;
				}
			}
		}
	}
	return false;
}

int DVD::FindFreeRegister() {
	for (int g=0; g<=12; g++) {
		if (!IsRegisterUsed(g))
			return g;
	}
	return -1;
}

bool DVD::IsRegisterUsed(int g) {
	wxString gstr = wxT("g") + wxString::Format(wxT("%d"),g);
	for (int tsi = -1; tsi<(int)GetTitlesets().Count(); tsi++) {
		for (int m=0; m<=1; m++) {
			bool menu = m == 0;
			if (tsi == -1 && !menu) // "titleset -1" contains only vmMenus
				continue;
			PgcArray& pgcs = GetPgcArray(tsi, menu);
			for (int pgci = 0; pgci<(int)pgcs.Count(); pgci++) {
				Pgc* pgc = pgcs[pgci];
				if (pgc->GetPostCommands().Find(gstr) != -1
						|| pgc->GetPreCommands().Find(gstr) != -1)
					return true;
				for (int vobi = 0; vobi<(int)pgc->GetVobs().Count(); vobi++) {
					Vob* vob = pgc->GetVobs()[vobi];
					if (vob->GetMenu()) {
						Menu* menu = vob->GetMenu();
						for (int obji = 0; obji<(int)menu->GetObjectsCount(); obji++) {
							MenuObject* obj = menu->GetObject(obji);
							if (obj->HasCustomAction() && obj->GetCustomAction().Find(gstr) != -1)
								return true;
						}
					}
				}
			}
		}
	}
	return false;
}

wxArrayString DVD::GetVideoFormatStrings()
{
  wxArrayString formats;
  formats.Add(_T("PAL 720x576"));
  formats.Add(_T("NTSC 720x480"));
  return formats;
}

wxArrayString DVD::GetAudioFormatStrings()
{
  wxArrayString formats;
  formats.Add(_T("MP2"));
  formats.Add(_T("AC3"));
  return formats;
}
