/* file job_control.c
 *
 * does calling of the ripper or encoder
 * handles files
 *
 * Ralf Engels  10/06/1999  changed lock file to handle all errors
 */
#include "config.h"

#include <stdio.h>		/* for bezo debug */
#include <errno.h>            /* for errno -- Really?  No shit... */

#ifdef STDC_HEADERS
# include <stdlib.h>
# include <stddef.h>
#else
# ifdef HAVE_STDLIB_H
#  include <stdlib.h>
# endif
#endif

#ifdef HAVE_SYS_TYPES_H
# include <sys/types.h>
#endif

#ifdef HAVE_SYS_STAT_H
# include <sys/stat.h>         /* defines read and write flags */
#endif

#ifdef TIME_WITH_SYS_TIME
# include <sys/time.h>
# include <time.h>
#else
# ifdef HAVE_SYS_TIME_H
#  include <sys/time.h>
# else
#  include <time.h>
# endif
#endif

#ifdef HAVE_UNISTD_H
# include <sys/types.h>
# include <unistd.h>
#endif

#include <signal.h>

#ifdef HAVE_FCNTL_H
# include <fcntl.h>
#endif

#ifdef HAVE_SYS_WAIT_H
# include <sys/wait.h>
#endif
#ifndef WEXITSTATUS
# define WEXITSTATUS(stat_val) ((unsigned int) (stat_val) >> 8)
#endif
#ifndef WIFEXITED
# define WIFEXITED(stat_val) (((stat_val) & 255) == 0)
#endif

#ifdef HAVE_STRING_H
# if !defined STDC_HEADERS && defined HAVE_MEMORY_H
#  include <memory.h>
# endif
# include <string.h>
#else
# ifdef HAVE_STRINGS_H
#  include <strings.h>
# endif
#endif

#include <glib.h>
#include <glib/gi18n.h>
#include <gtk/gtk.h>
#include <tag.h>
#include <fileref.h> 

#include "misc_utils.h"
#include "interface_common.h"
#include "ripper_encoder_manipulation.h"
#include "main_window_handler.h"
#include "select_frame_handler.h"
#include "status_frame_handler.h"
#include "job_control.h"
#include "calc_stat.h"

#define CLEAR_PIPE_BUF_SIZE         512
#define COUNT_BEFORE_GET_AVG        10

/* Dialog structure */
typedef struct
{
    char *title;
    char *msg;
} _dialog_data;


/* Function Prototypes */
int lock_file(char *file_name, int is_temp);

/* calculates statiscal info to report to the user */
// current is length_processes now

void job_controller_timeout_start();
int job_controller_timeout_update(gpointer anything);
void job_controller_timeout_stop();


int lock_file(char *file_name, int is_temp)
{
    int fd;
    char *temp;
    char buf[ MAX_FILE_NAME_LENGTH ];

    fd = open(file_name,
              O_CREAT | O_EXCL,
              S_IWUSR | S_IRUSR | S_IRGRP | S_IWGRP | S_IROTH);

    if(fd >= 0)
    {
        close(fd);
        unlink(file_name);
    }
    else
    {
        if(errno == EEXIST)
            if(config.ask_when_file_exists == FALSE || is_temp == TRUE)
            {
                if(config.make_mp3_from_existing_wav)
                {
                    return 1;
                }

                /* Prepend config.prepend_char until we succeed open() */
                if(strlen(file_name) >
                        MAX_FILE_NAME_LENGTH + MAX_FILE_PATH_LENGTH - 2)
                {
                    return - 1;
                }
                else
                {
                    temp = file_name_without_path(file_name);
                    strcpy(buf, temp);
                    temp[ 0 ] = config.prepend_char;
                    strcpy(temp + 1, buf);

                    /* now try again */
                    if(lock_file(file_name, is_temp) < 0)
                    {
                        return - 1;
                    }
                }
            }
            else
            {
                if(config.make_mp3_from_existing_wav)
                {
                    return 1;
                }

                if(dialog_handler(WIDGET_CREATE, FALSE,
                                  DL_OVERWRITE_CONFIRM,
                                  FALSE,
                                  file_name_without_path(file_name),
                                  NULL, 0) == TRUE)
                    /* Just return. Cdparanoia or 8hz-mp3 will overwrite on it */
                {
                    return 0;
                }

                /* Let's ask the user what s/he wants */
                temp = file_name_without_path(file_name);
                strcpy(buf, temp);

                if(dialog_handler(WIDGET_CREATE, TRUE,
                                  DL_ENTER_FILE_NAME,
                                  TRUE,
                                  buf, buf, sizeof(buf) - 1) == FALSE)
                    /* The user does not want to continue. return error */
                {
                    return - 1;
                }

                strcpy(temp, buf);

                /* now try again */
                if(lock_file(file_name, is_temp) < 0)
                {
                    return - 1;
                }
            }
        else /* an other error (maybe directory not existent */
        {
            err_handler(CREATING_FILE_ERROR, file_name);
            return - 1;
        }
    }

    return 0;
}

/* this is called when the go button is clicked. The default file names are determined
   and the files are locked. */
void job_starter(_main_data *main_data)
{
    int i, track, is_temp, code;
    static char *wav_file_path=0, *enc_file_path=0;
    CommonEncoderType type;

    /* Sync main_data structure with the data user has entered */
    select_frame_handler(SF_SYNC_MAIN_DATA, 0, main_data);

    /* Setup directory and filename extension */
    if(!create_filenames_from_format(main_data))
    {
        return;
    }

    /* Reset exist flags */
    for(i = 0; i < main_data->num_tracks(); i++)
    {
        if(main_data->track[ i ].make_wav)
        {
            main_data->track[ i ].wav_exist = FALSE;
        }

        if(main_data->track[ i ].make_mp3)
        {
            main_data->track[ i ].mp3_exist = FALSE;
        }
    }

    /* Lock target files */
    track = -1;
    type = WAV;

    while(main_data->find_next_job(track, WAV, &track, &type) >= 0)
    {
        is_temp = FALSE;

        if(!create_file_names_for_track(main_data, track, &wav_file_path, NULL))
        {
            return;
        }

        code = lock_file(wav_file_path, is_temp);

        if(code < 0)
        {
            return;
        }

        if(code == 1)
        {
            main_data->track[ track ].make_wav = FALSE;
            main_data->track[ track ].wav_exist = TRUE;
        }
    }

    if(main_data->track[ track ].make_mp3)
    {
        track = -1;
        type = MP3;

        while(main_data->find_next_job(track, MP3, &track, &type) >= 0)
        {
            if(!create_file_names_for_track(main_data, track, NULL, &enc_file_path))
            {
                return;
            }

            if(lock_file(enc_file_path, FALSE) < 0)
            {
                return;
            }
        }
    }

    /* Destroy select frame & change main window button state */
    select_frame_handler(WIDGET_DESTROY, 0, NULL);
    main_window_handler(MW_MODE_STATUS, NULL, NULL);
    /* Start Job */
    job_controller(JC_START, main_data);
}


static int timer;

void job_controller_timeout_start()
{
    timer = gtk_timeout_add(JC_TIMEOUT, job_controller_timeout_update, NULL);
}


int job_controller_timeout_update(gpointer anything)
{
    job_controller(JC_UPDATE, NULL);
    return TRUE;
}


void job_controller_timeout_stop()
{
    gtk_timeout_remove(timer);
}


void job_controller(JC ops, _main_data *main_data)
{
    static CommonEncoderType cur_type = NO_TYPE;
    static int wav_cur_track = -1;
    static int mp3_cur_track = -1;
    static int mp3_nxt_track = -1;
    static pid_t wav_pg_pid = -1;
    static pid_t mp3_pg_pid = -1;
    static pid_t wav_pi_pid = -1;
    static pid_t mp3_pi_pid = -1;
    static int wav_read_fd;
    static int mp3_read_fd;
    static _stat stat;
    static _main_data *saved_main_data;
    static char *wav_file_path, *enc_file_path;
    static CalcStat calc_stat;
    unsigned wav_current = 0;
    unsigned mp3_current = 0;
    double wav_progress;
    double mp3_progress;
    char msg[ MAX_PLUGIN_OUTPUT_LENGTH ];
    char *str;
    int temp, temp_track;
    
    switch(ops)
    {
        case JC_START :
            /* called once when the go button is clicked. */
            saved_main_data = main_data;

            if(wav_cur_track != -1)
            {
                err_handler(JOB_IN_PROGRESS_ERR, NULL);
                main_data->finish_job();
                return;
            }

            if(main_data->find_next_job(wav_cur_track, WAV,
                             &wav_cur_track, &cur_type) < 0)
            {
                /* ok, no more wavs to rip, try looking for mp3s */
                if(main_data->find_next_job(mp3_cur_track, MP3,
                                 &mp3_cur_track, &cur_type) < 0)
                {
                    /* nothing at all found */
                    err_handler(NOTHING_TO_DO_ERR, NULL);
                    main_data->finish_job();
                    return;
                }

                create_file_names_for_track(main_data, mp3_cur_track,
                                            &wav_file_path, &enc_file_path);

                calc_stat.start_session(main_data, &stat);
                calc_stat.start(main_data, &stat, mp3_cur_track, MP3);

                /* start encoding */
                if(start_ripping_encoding(MP3, main_data->track[ mp3_cur_track ].begin,
                                          main_data->track[ mp3_cur_track ].length,
                                          mp3_cur_track,
                                          wav_file_path,
                                          enc_file_path,
                                          &mp3_pg_pid,
                                          &mp3_pi_pid,
                                          &mp3_read_fd) < 0)
                {
                    main_data->finish_job();
                    return;
                }

                stat.encoding = TRUE;
            }
            else
            {
                /* found the first wav to rip */
                create_file_names_for_track(main_data, wav_cur_track, &wav_file_path, &enc_file_path);
                calc_stat.start_session(main_data, &stat);
                calc_stat.start(main_data, &stat, wav_cur_track, WAV);

                /* start ripping */
                if(start_ripping_encoding(WAV, main_data->track[ wav_cur_track ].begin,
                                          main_data->track[ wav_cur_track ].length,
                                          wav_cur_track,
                                          wav_file_path,
                                          enc_file_path,
                                          &wav_pg_pid,
                                          &wav_pi_pid,
                                          &wav_read_fd) < 0)
                {
                    main_data->finish_job();
                    return;
                }

                stat.ripping = TRUE;

                /* Find the next track to encode if any */
                if(main_data->find_next_job(mp3_cur_track, MP3, &mp3_nxt_track, &cur_type) < 0 ||
                        !main_data->track[ mp3_nxt_track ].make_mp3)
                {
                    mp3_nxt_track = -1;
                }
            }

            /* Create wav/mp3 status frame */
            wm_status_frame_handler(WIDGET_CREATE, cur_type, &stat, NULL);

            job_controller_timeout_start();
            return;

        case JC_UPDATE :
            main_data = saved_main_data;

            /* Part 1: check progress on the rip */
            if(stat.ripping == TRUE)
            {
                temp = read_and_process_plugin_output(wav_read_fd, &wav_progress, msg);
                wav_current = main_data->track[ wav_cur_track ].length * wav_progress;

                switch(temp)
                {
                    case PLUGIN_MSG_PARSE_ERR :
                        /* Nothing to do. Let's wait more */
                        break;

                    case PLUGIN_PROGRESS_MSG :
                        /* progress report, update status display */
                        calc_stat.update(main_data, &stat, wav_current,
                                  wav_cur_track, WAV);

                        /* Update status widget */
                        if(msg[ 0 ] == '\0')
                        {
                            str = NULL;
                        }
                        else
                        {
                            str = msg;
                        }

                        wm_status_frame_handler(WIDGET_UPDATE, WAV, &stat, str);
                        break;

                    case PLUGIN_NO_MSG_AVAILABLE :

                        /* Check if the plugin has exited
                           It only happens when ripperX failed to execute the plugin */
                        if(wav_pi_pid >= 0)
                            if(waitpid(wav_pi_pid, NULL, WNOHANG) == wav_pi_pid)
                            {
                                err_handler(PLUGIN_NOT_PRESENT_ERR,
                                            _("Maybe ripperX has failed to execute the plugin"));
                                wav_pi_pid = -1;
                                job_controller_timeout_stop();
                                job_controller(JC_ABORT_ALL_DELETE, main_data);
                                return;
                            }

                        /* Check if the job is finished */
                        if(waitpid(wav_pg_pid, NULL, WNOHANG) == wav_pg_pid)
                        {
                            /* One job finished, go for next one */

                            /* kill the plugin */
                            if(waitpid(wav_pi_pid, NULL, WNOHANG) != wav_pi_pid)
                            {
                                kill(-wav_pi_pid, SIGTERM);
                                waitpid(wav_pi_pid, NULL, 0);
                            }

                            /* Close the fd */
                            close(wav_read_fd);

                            /* stop calculating stats */
                            calc_stat.stop(main_data, &stat, WAV);

                            /* Mark that it exists */
                            main_data->track[ wav_cur_track ].wav_exist = TRUE;

                            /* find next track to rip */
                            temp_track = wav_cur_track;

                            if(main_data->find_next_job(wav_cur_track, WAV, &wav_cur_track, &cur_type) < 0)
                            {
                                /* All finished - no more rips */
                                wav_pg_pid = -1;
                                wav_pi_pid = -1;
                                wav_cur_track = -1;
                                cur_type = NO_TYPE;
                                stat.ripping = FALSE;

                                /* if we are only ripping, finish up for good */
                                if(!main_data->track[ temp_track ].make_mp3)
                                {
                                    calc_stat.stop_session(&stat);

                                    job_controller_timeout_stop();
                                    main_data->finish_job();
                                    return;
                                }
                            }
                            else
                            {
                                /* if we are only ripping, update the stats */
                                if(!main_data->track[ temp_track ].make_mp3)
                                {
                                    stat.tracks_done++;
                                    stat.tracks_remain--;
                                }

                                /*  start ripping the next track */
                                create_file_names_for_track(main_data, wav_cur_track,
                                                            &wav_file_path, &enc_file_path);
                                calc_stat.start(main_data, &stat, wav_cur_track, WAV);

                                if(start_ripping_encoding(WAV,
                                                          main_data->track[ wav_cur_track ].begin,
                                                          main_data->track[ wav_cur_track ].length,
                                                          wav_cur_track,
                                                          wav_file_path,
                                                          enc_file_path,
                                                          &wav_pg_pid, &wav_pi_pid,
                                                          &wav_read_fd) < 0)
                                {
                                    calc_stat.stop_session(&stat);
                                    job_controller_timeout_stop();
                                    main_data->finish_job();
                                    return;
                                }
                            }
                        } /* end if job is finished section */

                        break;
                } /* end rip switch */
            } /* end rip progress check */

            /* Part 2: check progress on the encode */
            if(stat.encoding == TRUE)
            {
                temp = read_and_process_plugin_output(mp3_read_fd, &mp3_progress, msg);
                mp3_current = main_data->track[ mp3_cur_track ].length * mp3_progress;

                switch(temp)
                {
                    case PLUGIN_MSG_PARSE_ERR :
                        /* Nothing to do. Let's wait more */
                        break;

                    case PLUGIN_PROGRESS_MSG :
                        /* progress report, update status display */
                        calc_stat.update(main_data, &stat, mp3_current,
                                  mp3_cur_track, MP3);

                        /* Update status widget */
                        if(msg[ 0 ] == '\0')
                        {
                            str = NULL;
                        }
                        else
                        {
                            str = msg;
                        }

                        wm_status_frame_handler(WIDGET_UPDATE, MP3, &stat, str);
                        break;

                    case PLUGIN_NO_MSG_AVAILABLE :

                        /* Check if the plugin has exited
                           It only happens when ripperX failed to execute the plugin */
                        if(mp3_pi_pid >= 0)
                            if(waitpid(mp3_pi_pid, NULL, WNOHANG) == mp3_pi_pid)
                            {
                                err_handler(PLUGIN_NOT_PRESENT_ERR,
                                            _("Maybe ripperX has failed to execute the plugin"));
                                mp3_pi_pid = -1;
                                job_controller_timeout_stop();
                                job_controller(JC_ABORT_ALL_DELETE, main_data);
                                return;
                            }

                        /* Check if the job is finished */
                        if(waitpid(mp3_pg_pid, NULL, WNOHANG) == mp3_pg_pid)
                        {
                            /* One job finished, go for next one */

                            /* kill the plugin */
                            if(waitpid(mp3_pi_pid, NULL, WNOHANG) != mp3_pi_pid)
                            {
                                kill(-mp3_pi_pid, SIGTERM);
                                waitpid(mp3_pi_pid, NULL, 0);
                            }

                            /* Close the fd */
                            close(mp3_read_fd);

                            /* stop calculating stats */
                            calc_stat.stop(main_data, &stat, MP3);

                            main_data->track[ mp3_cur_track ].mp3_exist = TRUE;

                            /* Delete WAV file if he/she doesn't want it */
                            if(!config.keep_wav)
                            {
                                create_file_names_for_track(main_data, mp3_cur_track,
                                                            &wav_file_path, &enc_file_path);

                                if(unlink(wav_file_path) < 0)
                                {
                                    err_handler(FILE_DELETE_ERR, wav_file_path);
                                }

                                /* Mark that it has been deleted */
                                main_data->track[ mp3_cur_track ].wav_exist = FALSE;

                                /* Delete WAV work directory if this was the last WAV file,
                                   and the mp3 work dir is different */
                                if(stat.tracks_remain <= 1)
                                    if(strcmp(config.wav_path,config.mp3_path) != 0)
                                    {
                                        rmdir(file_path_without_name(wav_file_path));
                                    }
                            }

                            /* find next track to encode */
                            temp_track = mp3_cur_track;

                            if(main_data->find_next_job(mp3_cur_track, MP3, &mp3_nxt_track, &cur_type) < 0)
                            {
                                /* All finished - no more encoding - done for good! */
                                mp3_pg_pid = -1;
                                mp3_pi_pid = -1;
                                mp3_cur_track = mp3_nxt_track = -1;
                                cur_type = NO_TYPE;
                                calc_stat.stop_session(&stat);
                                job_controller_timeout_stop();
                                stat.encoding = FALSE;

                                main_data->finish_job();
                                return;
                            }

                            mp3_cur_track = -1;

                            stat.tracks_done++;
                            stat.tracks_remain--;
                            stat.encoding = FALSE;

                        } /* end if job is finished section */

                        break;
                } /* end encode switch */
            } /* end encode progress check */

            if(!stat.encoding && mp3_nxt_track != -1 && main_data->track[ mp3_nxt_track ].wav_exist)
            {
                /* start encoding */
                mp3_cur_track = mp3_nxt_track;
                mp3_nxt_track = -1;
                create_file_names_for_track(main_data, mp3_cur_track, &wav_file_path, &enc_file_path);
                calc_stat.start(main_data, &stat, mp3_cur_track, MP3);

                if(start_ripping_encoding(MP3, main_data->track[ mp3_cur_track ].begin,
                                          main_data->track[ mp3_cur_track ].length,
                                          mp3_cur_track,
                                          wav_file_path,
                                          enc_file_path,
                                          &mp3_pg_pid, &mp3_pi_pid,
                                          &mp3_read_fd) < 0)
                {
                    calc_stat.stop_session(&stat);
                    job_controller_timeout_stop();
                    main_data->finish_job();
                }

                stat.encoding = TRUE;
            }

            /* end of JC_UPDATE */
            return;
        case JC_PAUSE :
            main_data = saved_main_data;

            if(wav_pg_pid >= 0)
                if(waitpid(wav_pg_pid, NULL, WNOHANG) != wav_pg_pid)
                {
                    kill(wav_pg_pid, SIGTSTP);
                }

            if(wav_pi_pid >= 0)
                if(waitpid(wav_pi_pid, NULL, WNOHANG) != wav_pi_pid)
                {
                    kill(wav_pi_pid, SIGTSTP);
                }

            if(mp3_pg_pid >= 0)
                if(waitpid(mp3_pg_pid, NULL, WNOHANG) != mp3_pg_pid)
                {
                    kill(mp3_pg_pid, SIGTSTP);
                }

            if(mp3_pi_pid >= 0)
                if(waitpid(mp3_pi_pid, NULL, WNOHANG) != mp3_pi_pid)
                {
                    kill(mp3_pi_pid, SIGTSTP);
                }

            calc_stat.pause();
            job_controller_timeout_stop();
            return;

        case JC_CONT :
            main_data = saved_main_data;

            if(wav_pg_pid >= 0)
                if(waitpid(wav_pg_pid, NULL, WNOHANG) != wav_pg_pid)
                {
                    kill(wav_pg_pid, SIGCONT);
                }

            if(wav_pi_pid >= 0)
                if(waitpid(wav_pi_pid, NULL, WNOHANG) != wav_pi_pid)
                {
                    kill(wav_pi_pid, SIGCONT);
                }

            if(mp3_pg_pid >= 0)
                if(waitpid(mp3_pg_pid, NULL, WNOHANG) != mp3_pg_pid)
                {
                    kill(mp3_pg_pid, SIGCONT);
                }

            if(mp3_pi_pid >= 0)
                if(waitpid(mp3_pi_pid, NULL, WNOHANG) != mp3_pi_pid)
                {
                    kill(mp3_pi_pid, SIGCONT);
                }

            calc_stat.cont();
            job_controller_timeout_start();
            return;

        case JC_ABORT :
        case JC_ABORT_DELETE :
        case JC_ABORT_ALL :
        case JC_ABORT_ALL_DELETE :
            main_data = saved_main_data;

            if(wav_pg_pid >= 0)
                if(waitpid(wav_pg_pid, NULL, WNOHANG) != wav_pg_pid)
                {
                    job_controller(JC_CONT, NULL);
                    kill(-wav_pg_pid, SIGTERM);
                    waitpid(wav_pg_pid, NULL, 0);
                }

            if(wav_pi_pid >= 0)
                if(waitpid(wav_pi_pid, NULL, WNOHANG) != wav_pi_pid)
                {
                    kill(-wav_pi_pid, SIGTERM);
                    waitpid(wav_pi_pid, NULL, 0);
                }

            if(mp3_pg_pid >= 0)
                if(waitpid(mp3_pg_pid, NULL, WNOHANG) != mp3_pg_pid)
                {
                    job_controller(JC_CONT, NULL);
                    kill(-mp3_pg_pid, SIGTERM);
                    waitpid(mp3_pg_pid, NULL, 0);
                }

            if(mp3_pi_pid >= 0)
                if(waitpid(mp3_pi_pid, NULL, WNOHANG) != mp3_pi_pid)
                {
                    kill(-mp3_pi_pid, SIGTERM);
                    waitpid(mp3_pi_pid, NULL, 0);
                }

            /* Close the pipe or pty */
            close(wav_read_fd);
            close(mp3_read_fd);
            calc_stat.stop_session(&stat);

            /* Destroy status widget */
            wm_status_frame_handler(WIDGET_DESTROY, WAV, &stat, msg);

            if((ops == JC_ABORT_ALL_DELETE) || (ops == JC_ABORT_DELETE))
            {
                create_file_names_for_track(main_data, wav_cur_track, &wav_file_path, NULL);

                if(wav_cur_track != -1)
                {
                    create_file_names_for_track(main_data, wav_cur_track, &wav_file_path, &enc_file_path);

                    if(unlink(wav_file_path) < 0)
                    {
                        err_handler(FILE_DELETE_ERR, wav_file_path);
                    }
                }

                if(mp3_cur_track != -1)
                {
                    create_file_names_for_track(main_data, mp3_cur_track, NULL, &enc_file_path);

                    if(unlink(enc_file_path) < 0)
                    {
                        err_handler(FILE_DELETE_ERR, enc_file_path);
                    }
                }
            }
            else
            {
                /* Mark that it exists */
                if(wav_cur_track != -1)
                {
                    main_data->track[ wav_cur_track ].wav_exist = TRUE;
                }

                if(mp3_cur_track != -1)
                {
                    main_data->track[ mp3_cur_track ].mp3_exist = TRUE;
                }
            }

            wav_cur_track = -1;
            mp3_cur_track = -1;
            cur_type = NO_TYPE;
            calc_stat.stop_session(&stat);
            job_controller_timeout_stop();
            main_data->finish_job();
            return;
    }
}
