/* vim: set sw=3 ts=3 sts=3 expandtab: */
#include "ecdb.h"

typedef struct Burn_Data Burn_Data;
struct Burn_Data
{
   BurnDisc *disc;
   BurnSession *session;
   Eina_List *sources;
   Eina_List *tracks;
   Ecdb_Project *proj;
   Ecdb_Page *page;
};

int ecdb_burn_finished(void *data, int type, void *event);
int ecdb_erase_project_init(Ecdb_Erase_Project *proj);
static void ecdb_burn_progress_handler(void *data, void *buffer,
                     unsigned int nbyte);
void ecdb_sources_list_free(Eina_List *list);
void ecdb_tracks_list_free(Eina_List *list);

int
ecdb_burn_project(Ecdb_Burn_Project *proj, Ecdb_Page *page)
{
   char reasons[BURN_REASONS_LEN];
   int padding, i;
   Burn_Data *data;
   BurnTrack *track;
   BurnSource *source;
   BurnWriteOpts *opts;
   pthread_t progress_update;

   i = 0;
   data = calloc(1, sizeof(Burn_Data));
   if (!data)
   {
      EINA_ERROR_PWARN("Error: Cannot create burn data structure!\n");
      return FALSE;
   }
   data->proj = ECDB_PROJECT(proj);
   data->page = page;

   if (proj->burn_mode != BURN_AUDIO)
      padding = 300*1024;

   data->disc = burn_disc_create();
   data->session = burn_session_create();
   burn_disc_add_session(data->disc, data->session, BURN_POS_END);

   track = burn_track_create();
   burn_track_define_data(track, 0, padding, 1, proj->burn_mode);

   source = ecdb_image_project(proj);
   if (!source)
   {
      EINA_ERROR_PWARN("Failed to add any files to burn disc!\n");
      burn_session_free(data->session);
      burn_disc_free(data->disc);
      ecdb_sources_list_free(data->sources);
      ecdb_tracks_list_free(data->tracks);
      FREE(data);
      return ECDB_ERROR_IMAGE_CREATE;
   }

   if (burn_track_set_source(track, source) != BURN_SOURCE_OK)
   {
      EINA_ERROR_PWARN("Error: Cannot attach source object to track object!\n");
      burn_session_free(data->session);
      burn_disc_free(data->disc);
      ecdb_sources_list_free(data->sources);
      ecdb_tracks_list_free(data->tracks);
      FREE(data);
      return ECDB_ERROR_SOURCE_ATTACH;
   }

   burn_session_add_track(data->session, track, BURN_POS_END);
   data->sources = eina_list_append(data->sources, source);
   data->tracks = eina_list_append(data->tracks, track);

   opts = burn_write_opts_new(ECDB_PROJECT(proj)->drive->tangible[0].drive);
   burn_write_opts_set_perform_opc(opts, proj->opc);
   burn_write_opts_set_multi(opts, proj->multi);
   if (proj->simulate)
   {
      EINA_ERROR_PINFO("Simulating Burn!\n");
   }
   burn_write_opts_set_simulate(opts, proj->simulate);
   burn_drive_set_speed(ECDB_PROJECT(proj)->drive->tangible->drive, 0,
                                                         proj->speed);
   burn_write_opts_set_underrun_proof(opts, proj->underrun_proof);

   EINA_ERROR_PDBG("Searching for burn mode\n");
   if (burn_write_opts_auto_write_type(opts, data->disc, reasons, 0)
         == BURN_WRITE_NONE)
   {
      EINA_ERROR_PWARN("Error: Failed to find a suitable write mode"
                                                               " for disc!\n");
      burn_session_free(data->session);
      burn_disc_free(data->disc);
      burn_write_opts_free(opts);
      ecdb_sources_list_free(data->sources);
      ecdb_tracks_list_free(data->tracks);
      FREE(data);
      return ECDB_ERROR_WRITE_MODE;
   }

   burn_disc_write(opts, data->disc);
   burn_write_opts_free(opts);

   EINA_ERROR_PINFO("Disc now burning\n");
   ECDB_PROJECT(proj)->pipe = ecore_pipe_add(ecdb_burn_progress_handler, data);
   pthread_create(&progress_update, NULL, ecdb_drive_progress_update, proj);
   pthread_detach(progress_update);
   ECDB_PROJECT(proj)->ev_handler = ecore_event_handler_add
         (ECDB_DRIVE_ACTION_FINISHED, ecdb_burn_finished, data);

   return ECDB_ERROR_NONE;
}

/* This function is pretty naive... Should probably update it at some time */
void *
ecdb_drive_progress_update(void *data)
{
   const Ecdb_Project *proj;
   BurnProgress p;
   struct burn_drive *drive;

   proj = data;

   if (!proj->drive->tangible)
   {
      EINA_ERROR_PWARN("No tangible drive!\n");
      ecore_pipe_del(proj->pipe);
      pthread_exit(NULL);
   }
   drive = proj->drive->tangible[0].drive;

   EINA_ERROR_PDBG("Progress update active\n");
   while (burn_drive_get_status(drive, NULL) == BURN_DRIVE_SPAWNING)
   {
      usleep(100000);
   }

   while (burn_drive_get_status(drive, &p) != BURN_DRIVE_IDLE)
   {
      if (p.sectors > 0)
      {
         ecore_pipe_write(proj->pipe, &p, sizeof(p));
      }
      usleep(100000);
   }

   EINA_ERROR_PDBG("Closing pipe\n");
   ecore_pipe_write(proj->pipe, proj->pipe, sizeof(Ecore_Pipe));
   pthread_exit(NULL);
}

static void
ecdb_burn_progress_handler(void *data, void *buffer, unsigned int nbyte)
{
   BurnProgress *p;
   Evas_Object *swallow;
   Burn_Data *bdata;
   static int last_sector = 0;
   int percent;
   Edje_Message_Int_Set *progress_msg;
   Edje_Message_String finalize;

   if (nbyte != sizeof(BurnProgress))
   {
      ecore_event_add(ECDB_DRIVE_ACTION_FINISHED, NULL, NULL, NULL);
      EINA_ERROR_PDBG("Adding event to queue.\n");
      last_sector = 0;
      return;
   }
   else
   {
      p = buffer;
   }


   /* Libburn reports p->sector as being 0 right at the end of the job,
    * so store the last sector and use that to determine the proper
    * percentage/sector to set
    */
   if (last_sector <= p->sector)
   {
      last_sector = p->sector;
   }
   else
   {
      last_sector = p->sectors;
   }

   bdata = data;
   if (!bdata)
   {
      EINA_ERROR_PWARN("ecdb_burn_progress_handler: NULL bdata!\n");
      return;
   }

   if ((!bdata->page) || (!bdata->proj))
   {
      EINA_ERROR_PWARN("ecdb_burn_progress_handler: NULL page or proj!\n");
      return;
   }

   switch (bdata->proj->type)
   {
      case ECDB_AUDIO_PROJECT:
         swallow = bdata->page->audio;
         break;

      case ECDB_DATA_PROJECT:
         swallow = bdata->page->data;
         break;

      case ECDB_IMAGE_PROJECT:
         swallow = bdata->page->image;
         break;

      default:
         EINA_ERROR_PWARN("ecdb_burn_progress_handler: Unrecognized "
                                                         "project type!\n");
         return;
   }

   percent = (int)((double)(last_sector + 1) / (double)p->sectors * 100.0);
   if (percent >= 100)
   {
      finalize.str = "Finalizing disc...";
      edje_object_message_send(swallow, EDJE_MESSAGE_STRING, 1, &finalize);
   }
   else
   {
      progress_msg = alloca(sizeof(Edje_Message_Int_Set) + (4 * sizeof(int)));
      progress_msg->count = 5;
      progress_msg->val[0] = percent;
      progress_msg->val[1] = last_sector;
      progress_msg->val[2] = p->sectors;
      progress_msg->val[3] = (int)p->buffer_available;
      progress_msg->val[4] = (int)p->buffer_capacity;
      edje_object_message_send(swallow, EDJE_MESSAGE_INT_SET, 0, progress_msg);
   }
}

int
ecdb_burn_finished(void *data, int type, void *event)
{
   Burn_Data *proj;

   proj = data;

   EINA_ERROR_PDBG("Freeing source and tracks\n");
   ecdb_sources_list_free(proj->sources);
   ecdb_tracks_list_free(proj->tracks);

   EINA_ERROR_PDBG("Freeing session and disc\n");
   burn_session_free(proj->session);
   burn_disc_free(proj->disc);

   EINA_ERROR_PDBG("Releasing drive\n");
   burn_drive_release(proj->proj->drive->tangible[0].drive, 1);
   burn_drive_info_free(proj->proj->drive->tangible);
   EINA_ERROR_PINFO("Burn Complete\n");

   ecore_event_handler_del(proj->proj->ev_handler);
   proj->proj->ev_handler = NULL;
   ecore_pipe_del(proj->proj->pipe);
   proj->proj->pipe = NULL;

   switch (proj->proj->type)
   {
      case ECDB_AUDIO_PROJECT:
         EINA_ERROR_PWARN("How in the world did you get here?\n");
         //ecdb_burn_audio_cleanup(proj->page);
         break;

      case ECDB_DATA_PROJECT:
         ecdb_burn_data_cleanup(proj->page);
         break;

      case ECDB_IMAGE_PROJECT:
         ecdb_burn_image_cleanup(proj->page);
         break;

      default:
         EINA_ERROR_PWARN("ecdb_burn_finished: unknown project type!\n");
   }
   FREE(proj);

   return TRUE;
}

void
ecdb_sources_list_free(Eina_List *list)
{
   void *data;

   EINA_LIST_FREE(list, data)
      burn_source_free(data);
}

void
ecdb_tracks_list_free(Eina_List *list)
{
   void *data;

   EINA_LIST_FREE(list, data)
      burn_track_free(data);
}
