//This file is part of AcetoneISO. Copyright 2006,2007,2008,2009 Marco Di Antonio and Fabrizio Di Marco (acetoneiso@gmail.com)
//Copyright 2010/2011 Marco Di Antonio

//    AcetoneISO is free software: you can redistribute it and/or modify
//    it under the terms of the GNU General Public License as published by
//    the Free Software Foundation, either version 3 of the License, or
//    (at your option) any later version.
//
//    AcetoneISO is distributed in the hope that it will be useful,
//    but WITHOUT ANY WARRANTY; without even the implied warranty of
//    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//    GNU General Public License for more details.
//
//    You should have received a copy of the GNU General Public License
//    along with AcetoneISO.  If not, see <http://www.gnu.org/licenses/>.
#include <QtGui>
#include <QFileDialog>
#include <QCoreApplication>
#include <QByteArray>
#include <QUrl>
#include <QProcess>
#include <QDBusConnection>
#include <QDBusInterface>
#include <QDBusMessage>
#include <QList>
#include <QVariant>
#include <fcntl.h>
#include <iostream>
#include <QDebug>
#include "burn_iso_2_cd.h"
#include "acetoneiso.h"


/*
cancellare cd completo: wodim -verbose dev=/dev/sr0  blank=all
cancellare cd rapido: wodim -verbose dev=/dev/sr0  blank=fast
dice il tipo di media inserito: hal-get-property "--key volume.disc.type --udi $udi
*/

//
burniso2cd::burniso2cd( QWidget * parent, Qt::WFlags f) 
	: QDialog(parent, f)
{

    setupUi(this);
    is_erasing = false;
    loaded_success = false;
    QCoreApplication::setApplicationName("AcetoneISO");
    connect( start, SIGNAL( clicked() ), this, SLOT( start_erase() ) );
    connect( push_select_image, SIGNAL( clicked() ), this, SLOT( select_image() ) );

    textBrowser->clear();
    
    //database speed for cd-r/rw
    hash_speed[0] = "1";
    hash_speed[1] = "4";
    hash_speed[2] = "10";
    hash_speed[3] = "16";
    hash_speed[4] = "24";
    hash_speed[5] = "28";
    hash_speed[6] = "32";
    hash_speed[7] = "40";
    hash_speed[8] = "48";

    itemRemovedSpeed = 0;
    
    device_scan();

}




void burniso2cd::device_scan() {


/*creo la connessione dbus a hal*/
QDBusConnection conn = QDBusConnection::systemBus();
QDBusInterface hal("org.freedesktop.Hal", "/org/freedesktop/Hal/Manager", "org.freedesktop.Hal.Manager", conn);
/*cerco tutti i dispositivi storage.cdrom con hal via dbus*/
QDBusMessage msg = hal.call( "FindDeviceByCapability", "storage.cdrom");

QList<QVariant> devices = msg.arguments(); 
/*per ogni udi (dispositivo cdrom) che trovo, ricavo vendor, model, velocità e path*/

//disabilita tutto se non viene trovato almeno 1 periferica
int count = devices.count();
if (count < 1) {
 QMessageBox::warning(this, "AcetoneISO::warning", tr("No CD/DVD device found.\nIf you think this is a bug please report it."));
 disable_buttons();
 devices_combo->addItem( tr("No CD/DVD device found") );
 return;
}


QIcon icon_optical_drive( ":/images/drive-optical.png" );

//scannerizzo tutte le periferiche e le aggiungo nella combobox. questa funzione viene chiamato una sola volta.
QVariant name(devices);
int cc = 0;
int success = -1;
while (cc < count) {
		  QString cdrom = name.toStringList()[0]; 
		  if (cdrom.isEmpty()) {
		    QMessageBox::warning(this, "AcetoneISO::warning", tr("No CD/DVD device found.\nIf you think this is a bug please report it."));
		    disable_buttons();
		    devices_combo->addItem( tr("No CD/DVD device found") );
		    return; 
		  }
		//  qDebug() << "Found device: " << cdrom; 
		  QDBusInterface device("org.freedesktop.Hal", cdrom, "org.freedesktop.Hal.Device", conn);
		  
		  //vedo se effittivamente il device puo scrivere su dischi cd-rw
		  msg = device.call("GetProperty", "storage.cdrom.cdr");
		  QVariant vcan_write_cdrw = msg.arguments()[0];
		  bool can_write_cdrw = vcan_write_cdrw.toBool();
		  if (!can_write_cdrw) {
		    cc = cc + 1;
		    continue;
		  }
		  //success si ricorda sempre la posizione in cui aggiungere all'array indipendentemente dal cc del ciclo while
		  success = success + 1;
		  //aggiungo id nell'array
		  id_device.insert(success, cdrom);		  
		  /*ottengo il nome del vendor del cdrom*/
		  msg = device.call("GetProperty", "storage.vendor");
		  QVariant var = msg.arguments()[0]; 
		  QString vendor = var.toStringList()[0];
		  //qDebug() << "VENDOR" << vendor;
		  /*ottengo il nome del modello del cdrom*/
		  msg = device.call("GetProperty", "storage.model");
		  QVariant var2 = msg.arguments()[0]; 
		  QString model = var2.toStringList()[0];
		  //qDebug() << "MODEL" << model;

		  /*ottengo il path del dispositivo cdrom*/
		  msg = device.call("GetProperty", "block.device");
		  QVariant var3 = msg.arguments()[0]; 
		  device_path.insert(success, var3.toStringList()[0]);
		  //qDebug() << "PATH" << device_path;
		  
		  msg = device.call("GetProperty", "storage.firmware_version");
		  QVariant firmwarev = msg.arguments()[0];
		  QString firmware = firmwarev.toString();
		   /*aggiungo il nome del device nella combobox*/
		  QString final_device = vendor.append( "\t" + model + " " + firmware );
		  devices_combo->insertItem(success,icon_optical_drive, " " + final_device );
		  
		  cc = cc + 1;
		  }

media_available();
//if < 1 significa che sebbene ci sono 1 o piu periferiche cdrom, nessuna e' capace di scrivere su cd-rw
if (devices_combo->count() < 1) {
disable_buttons();
QMessageBox::warning(this, "AcetoneISO::warning", tr("No CD/DVD device found capable of writing to CD-R/RW discs.\nIf you think this is a bug please report it."));
return; 
}
else { //c'e' almeno 1 periferica cdrom in grado di scrivere su dischi cd-rw
loaded_success = true;
//crea un timer cosi aggiorna automaticamente la label_info
timer = new QTimer(this);
connect(timer, SIGNAL(timeout()), this, SLOT(media_available()));
timer->start(1500);
connect( devices_combo, SIGNAL(currentIndexChanged(int) ), this , SLOT( combo_changed(int) ) ); 
}

}


void burniso2cd::media_available() {
//qDebug() << "media_available()";
//se sto masterizzando deve uscire da qui
if (is_erasing) {
start->setEnabled(false);
label_info->setText(tr("AcetoneISO is burning your image to the") + " " + disc_real_type + "..." );
return;
}

//if < 1 it means there are no devices capaci di scrivere su dischi cd-rw
if (devices_combo->count() < 1) {
  clean_combobox_speed();
  return; 
}

//se non e' visibile deve disconnettere le connessioni
if (loaded_success) {
  if (!devices_combo->isVisible()) {
    disconnect(devices_combo, 0, 0, 0);
    disconnect(timer, 0, 0, 0);
    loaded_success = false;
  }
}


estimated_time() ;

//scopro se ho un cd dentro il device
/*creo la connessione dbus a hal*/
QDBusConnection conn = QDBusConnection::systemBus();
QDBusMessage msg;
//prendo indice corrente della combobox cosi so chi l'id della periferica selezionata
int current_index = devices_combo->currentIndex();
QString id = id_device[current_index];

//mi connetto alla periferica corrente selezionata in combobox
QDBusInterface device("org.freedesktop.Hal", id, "org.freedesktop.Hal.Device", conn);
//vedo se e' inserito un cd 
msg = device.call("GetProperty", "storage.removable.media_available");
QVariant var5 = msg.arguments()[0];
bool opticDev = var5.toBool();
if (!opticDev) {
  label_info->setText(tr("Insert a CD-R/RW disc."));
  start->setEnabled(false);
  clean_combobox_speed();
  //devices_combo->setItemText(cc,tr("No Media Inserted, insert a media and click on refresh button."));
  return;
}

//vedo il tipo di cd inserito
QDBusMessage fbp;
QDBusInterface newh("org.freedesktop.Hal", "/org/freedesktop/Hal/Manager", "org.freedesktop.Hal.Manager", conn);
fbp = newh.call("FindDeviceStringMatch", "info.parent", id);
QList<QVariant> var6 = fbp.arguments();
QString disc_type = var6.at(0).toString();
QDBusInterface device_type("org.freedesktop.Hal", disc_type, "org.freedesktop.Hal.Device", conn);

//ferma in caso che il disco inserito non sia un disco CD-R/RW
msg = device_type.call("GetProperty", "volume.disc.type");
QString var7 = msg.arguments()[0].toString();
QString disc_is = var7;
if ( (disc_is != "cd_rw") and (disc_is != "cd_r" ) ) {
  label_info->setText(tr("The disc isn't a CD-R/RW. Please insert a CD-R/RW disc."));
  start->setEnabled(false); 
  clean_combobox_speed();
  return;
}

if ( disc_is == "cd_r") {
  disc_real_type = "CD-R";
}
else {
   disc_real_type = "CD-RW";
   msg = device.call("GetProperty", "storage.cdrom.cdrw");
   QVariant vcan_write_cdrw = msg.arguments()[0];
  bool discobool = vcan_write_cdrw.toBool();
    if (!discobool) {
      label_info->setText( tr("You inserted a CD-RW disc, however your CD/DVD device is uncapable of writing to CD-RW discs.") ); 
      start->setEnabled(false); 
      clean_combobox_speed();
      return;
    }
}

//vedo se il disco e' vuoto
msg = device_type.call("GetProperty", "volume.disc.is_blank");
bool disc_blank = msg.arguments()[0].toBool();
if (!disc_blank) {
  if ( disc_is == "cd_r") {
    disc_real_type = "CD-R";
    label_info->setText(tr("The CD-R isn't empty. Please insert an empty CD-R/RW disc."));
    start->setEnabled(false); 
    clean_combobox_speed();
    return;
  }
  else {
    label_info->setText(tr("The CD-RW isn't empty.\nPlease insert an empty CD-R/RW disc or blank the CD-RW with the appropriate AcetoneISO's blanking tool."));
    start->setEnabled(false); 
    clean_combobox_speed();
    return; 
  }
}



//check and set combobox speed
  msg = device.call("GetProperty", "storage.cdrom.write_speeds");
  QVariant vspeed = msg.arguments()[0];
  QStringList slist = vspeed.toStringList();
  int count_speed = slist.count();
  int count_combobox = (combo_speed->count() ) + itemRemovedSpeed;
  //put in combobox only if first time
  if ( count_speed != count_combobox ) {
    clean_combobox_speed();
    int count_cycle = 0;
    while (count_cycle < count_speed) {
      QString string_speed = slist[count_cycle];
      qreal q_speed = string_speed.toFloat() / 1024 ;
      int int_speed = qRound(q_speed);
      if (int_speed > 8) {
	int_speed = 8;
      }
      string_speed =  hash_speed[int_speed] + "x";
      //qDebug() <<  string_speed;
      arraySpeed << string_speed; //put speed in array
      count_cycle++;
    }
    //remove duplicate speeds from array and fill combobox speed
    itemRemovedSpeed = arraySpeed.removeDuplicates();
    int countArraySpeed = arraySpeed.count();
    int abc = 0;
    while (abc < countArraySpeed) {
      combo_speed->insertItem(abc, arraySpeed[abc] );
      abc = abc + 1;
    }
  }
  

  label_info->setText(disc_real_type + " " +  tr("succesfully found in device."));
  push_select_image->setEnabled(true);
  label_4->setEnabled(true);
  iso_to_burn_label->setEnabled(true);

}

//cleans combobox speed when something changed, example: user put new cd, changes drive, removes cd
void burniso2cd::clean_combobox_speed() {
  combo_speed->setEnabled(false);
  int count_combobox = combo_speed->count();
  int count_cycle = 0;
  while (count_cycle < count_combobox) {
    combo_speed->removeItem(count_cycle);
    count_cycle++;
  }
  combo_speed->setEnabled(true);
  
  //reset array speed
  arraySpeed.clear();
  //reset number items removed if duplicates speeds found
  itemRemovedSpeed= 0;
  
}


//gestisce quando l'utente cambia periferica nella combobox
void burniso2cd::combo_changed(int n) {
  int a;
  a = n;
  clean_combobox_speed();
  media_available();
}


//user wants to choose which image to burn
void burniso2cd::select_image() {
  QDir Home = QDir::home();// entro nella home dell'utente
  QString isoToBurnDialog;
  isoToBurnDialog = QFileDialog::getOpenFileName(this,tr("Select Image to Burn"), Home.path() , tr("Image Files ISO/CUE/TOC (*.iso *.cue *.toc)"));
  if ( isoToBurnDialog.isNull() )  {
    return;   
  }
  QFileInfo isoInfo(isoToBurnDialog);
  isoToBurnDialog = isoInfo.absoluteFilePath();
  imageToBurn = isoToBurnDialog;


  //image type?
  QStringList checkSupported;
  checkSupported << "iso" << "cue" << "toc";
  realSuffix = isoInfo.suffix();
  imageType = (isoInfo.suffix()).toLower();
  if (!checkSupported.contains(imageType) ) {
    QMessageBox::warning(this, "AcetoneISO::File Unsupported",tr("The Image you selected is unsupported from AcetoneISO."));
    start->setEnabled(false);
    return;   
  }
  
  if (imageType == "toc") {
    imageToBurn = isoInfo.absolutePath() +  "/" + isoInfo.completeBaseName ();
  }
  if (imageType == "cue") {
    imageToBurn = isoInfo.absolutePath() +  "/" + isoInfo.completeBaseName ();
  }
  
  
  QFile fileExists(imageToBurn);
  if (!fileExists.exists()) {
     QMessageBox::warning(this, "AcetoneISO::File Not Found",tr("The Image you selected does not exist.\nIf trying to burn a CUE/TOC image, be sure the image file is in the exact folder where the CUE/TOC file is.\nBe also sure they have same name except the CUE/TOC file ending with .toc extension."));
     start->setEnabled(false);
     return;
  }  
  
  float size = (isoInfo.size()  / 1024) / 1024;
  if (size > 700) {
    QMessageBox::warning(this, "AcetoneISO::File Too Big",tr("The Image you selected is too big to fit into a CD-R/RW."));
    start->setEnabled(false);
    return;
  }
  
  
  //se CUE, convertire con cue2toc
  //cue2toc file.cue > file.toc  or  direct output without >
  if (imageType == "cue") {
    QFile cueFile("/usr/bin/cue2toc");
    if (!cueFile.exists()) {
      QMessageBox::warning(this, "AcetoneISO::cue2toc not found",tr("Program cue2toc not found in /usr/bin\nPlease install cue2toc package."));
      start->setEnabled(false);
      return;
    }
    cue2tocP = new QProcess();
    cue2tocP->setReadChannel(QProcess::StandardOutput);
    cue2tocP->setProcessChannelMode(QProcess::MergedChannels);	
    //connection to update the display
    QDir bin("/usr/bin");
    QDir::setCurrent( bin.absolutePath() );
    QString cuecommand = "cue2toc \"" +  imageToBurn + "." + realSuffix + "\" -o \"" +  imageToBurn + ".toc\"";  
    cue2tocP->start(cuecommand );
    cue2tocP->waitForFinished();
    QFile newTocFile(imageToBurn + ".toc");
    if (!newTocFile.exists()) {
      QMessageBox::critical(this, "AcetoneISO::toc file not found",tr("Unable to generate toc file from the cue file you selected."));
      start->setEnabled(false);
      return;
    }
    imageType = "toc";
    realSuffix = "toc"; 
  }
  

  iso_to_burn_label->setText(imageToBurn);
}


//estimates time to burn image based on speed
void burniso2cd::estimated_time() {
  int count_combobox = combo_speed->count();
  if (count_combobox < 1) {
    estimated_label->setText("");
    push_select_image->setEnabled(false);
    label_4->setEnabled(false);
    iso_to_burn_label->setEnabled(false);
    start->setEnabled(false);
    return; 
  }
  imageToBurn = iso_to_burn_label->text();
  QFile fileExists(imageToBurn);
  if (!fileExists.exists()) {
     start->setEnabled(false);
     return;
  }
  start->setEnabled(true);
  QFileInfo isoInfo(imageToBurn);
  QString fileName = isoInfo.fileName();

  //estimated time: size / writing speed
  qint64 imageSize = (isoInfo.size() / 1024) / 1024;
  QString current_speed = combo_speed->currentText();
  current_speed = ( current_speed.split("x"))[0]  ;

  int key_hash = hash_speed.key(current_speed);
  if (key_hash < 1) {
   key_hash = 1; 
  }
  current_speed = QString::number(key_hash);
  
  float f_final_time = imageSize / current_speed.toFloat() + 16;
  int final_time = qRound(f_final_time);
  QString seconds_or_minutes;
  if ( f_final_time < 60) {
      seconds_or_minutes =  QString::number(final_time) + tr(" seconds");
  }
  else {
      f_final_time = f_final_time / 60;
      final_time = qRound(f_final_time);
      seconds_or_minutes = QString::number(final_time) + tr(" minutes");
  }
  
  estimated_label->setText( tr("Estimated time to burn ") + fileName + ": " +  seconds_or_minutes  ); 
}




void burniso2cd::start_erase() {
  QMessageBox msgBox;
  msgBox.setText(tr("You decided to burn the image on the") + " " + disc_real_type + "." + tr("\nAre you sure?"));
  msgBox.setStandardButtons(QMessageBox::Yes | QMessageBox::No);
  switch (msgBox.exec()) {
  case QMessageBox::Yes:
    {
      //continue below
     }
     break;
 case QMessageBox::No:
 {
  return; //exit
 }
     break;
 default:
     // should never be reached
     break;
 }

//wodim -v -dao -dummy -dev=/dev/sr0  -speed=6  gparted.iso
//cue/toc:  cdrdao simulate  --eject --speed 16 --device /dev/sr0 --driver generic-mmc my_game_rip.toc SIMULATE
//          cdrdao write  --eject --speed 16 --device /dev/sr0 --driver generic-mmc my_game_rip.toc  REAL WRITE


  disable_buttons();
  QString dev = device_path.at(devices_combo->currentIndex());
  dev = dev.prepend("dev=");
  textBrowser->clear(); 
  QString current_speed = combo_speed->currentText();
  current_speed = ( current_speed.split("x"))[0]  ;


  QString wodim = "";

  //realize if it must burn an ISO or TOC
  if (imageType == "toc") {
    //image is TOC
    if (simulation_checkbox->isChecked()) {
      wodim = "cdrdao simulate "; 
    }
    else {
       wodim = "cdrdao write "; 
    }
    if (eject->isChecked()) {
      wodim = wodim.append(" --eject "); 
    }
    wodim = wodim.append(" --speed " + current_speed  + " --device " +  (dev.split("="))[1]  + " --driver generic-mmc " + "  \"" + imageToBurn + "." + realSuffix + "\"" );
  }
  else {
  //image is ISO
    wodim = "wodim -v -dao " + dev + " -speed=" + current_speed ;
    if (simulation_checkbox->isChecked()) {
      wodim = wodim.append(" -dummy"); 
    }
    if (eject->isChecked()) {
      wodim = wodim.append(" -eject");   
    }
  
    wodim = wodim.append("  \"" + imageToBurn + "\"" ); 
  }
  
 // qDebug() << wodim;
  
  erase = new QProcess();
  erase->setReadChannel(QProcess::StandardOutput);
  erase->setProcessChannelMode(QProcess::MergedChannels);	
  //connection to update the display
  connect(erase, SIGNAL(readyReadStandardOutput()), SLOT(updateEraseDisplay() )); 
  connect(erase, SIGNAL(finished(int, QProcess::ExitStatus)), this, SLOT(printOutErase(int, QProcess::ExitStatus)));
  QDir bin("/usr/bin");
  QDir::setCurrent( bin.absolutePath() );
  is_erasing = true;

  erase->start(wodim );


}

//fa loutput nel display del processo wodim
void burniso2cd::updateEraseDisplay() {
  erase_output = erase->readAllStandardOutput(); 
  textBrowser->setPlainText(erase_output);   
}
//gestisce quando il processo wodim finisce
void burniso2cd::printOutErase(int, QProcess::ExitStatus) {
  iso_to_burn_label->setText("");
  enable_buttons();
  is_erasing = false;
  int valore_uscita = erase->exitCode();
if(valore_uscita == 0) {
  QMessageBox::information(this, "AcetoneISO",tr("Process Succesfully Finished!"));
  QWidget::close ();
}
else {
  QMessageBox::critical(this, "AcetoneISO","Process Error Code: " + QString::number(valore_uscita) );
}
}

//abilita i bottoni
void burniso2cd::enable_buttons() { 
  start->setEnabled(true);
  devices_combo->setEnabled(true);
  eject->setEnabled(true);
  simulation_checkbox->setEnabled(true);
  combo_speed->setEnabled(true);
  push_select_image->setEnabled(true);
}
//disabilita i bottoni
void burniso2cd::disable_buttons() {
  start->setEnabled(false);
  devices_combo->setEnabled(false);
  eject->setEnabled(false);
  simulation_checkbox->setEnabled(false);
  combo_speed->setEnabled(false);
  push_select_image->setEnabled(false);
}


void burniso2cd::play_success() {
  /*
  Phonon::MediaObject *mediaObject;	
  Phonon::AudioOutput *audioOutput; 
  Phonon::Path path;
  mediaObject = new Phonon::MediaObject();
  audioOutput = new Phonon::AudioOutput(Phonon::NoCategory);
  path = Phonon::createPath(mediaObject, audioOutput);
  QString file(":/audio/success.ogg");
  mediaObject->setCurrentSource(Phonon::MediaSource(file));
  mediaObject->play();
  */
}

void burniso2cd::play_error() {
  /*
  Phonon::MediaObject *mediaObject;	
  Phonon::AudioOutput *audioOutput; 
  Phonon::Path path;
  mediaObject = new Phonon::MediaObject();
  audioOutput = new Phonon::AudioOutput(Phonon::NoCategory);
  path = Phonon::createPath(mediaObject, audioOutput);
  QString file(":/audio/error.ogg");
  mediaObject->setCurrentSource(Phonon::MediaSource(file));
  mediaObject->play();
  */
}



