/* 
 * job.c
 *
 * Copyright (C) 1997,98 Rasca, Berlin
 * Copyright (C) 2003-06 Karl H. Beckers, Frankfurt
 *
 * This program 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 2 of the License, or
 * (at your option) any later version.
 *
 * This program 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 this program; if not, write to the Free Software
 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 *
 * This file defines the job data structure and some functions to
 * set/get info about a job (kinda tries to be OO ;S )
 *
 */

#ifdef HAVE_CONFIG_H
# include <config.h>
#endif

#include <stdio.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <unistd.h>
#include <errno.h>
#include <pthread.h>
#include <limits.h>             // PATH_MAX
#include <X11/Intrinsic.h>

#include "job.h"
#include "capture.h"
#include "xtoxwd.h"
#include "frame.h"
#include "colors.h"
#include "codecs.h"
#include "control.h"
#include "xvidcap-intl.h"
#ifdef USE_FFMPEG
# include "xtoffmpeg.h"
#endif                          // USE_FFMPEG


#define DEBUGFILE "job.c"

static Job *job;

extern xvCodec tCodecs[NUMCODECS];
extern xvFFormat tFFormats[NUMCAPS];

extern pthread_mutex_t recording_mutex;
extern pthread_cond_t recording_condition_unpaused;


int job_set_sound_dev(char *, int, int, int);
void job_set_fps(int fps);
int job_fps(void);
void job_set_file(char *file);
char *job_file(void);
void job_set_compression(int comp);
int job_compression(void);
void job_set_capture(void);
void job_set_quality(int quality);
int job_quality(void);


/* 
 * HELPER FUNCTIONS
 */
Job *xvc_job_new()
{
#define DEBUGFUNCTION "xvc_job_new()"

    job = (Job *) malloc(sizeof(Job));
    if (!job) {
        perror("%s %s: malloc failed?!?");
        exit(1);
    }
    return (job);
#undef DEBUGFUNCTION
}


void
xvc_job_set_from_app_data(AppData * app, Display * disp,
                          XWindowAttributes wa)
{
#undef DEBUGFUNCTION
#define DEBUGFUNCTION "xvc_job_set_from_app_data()"
    CapTypeOptions *cto;
    char file[PATH_MAX + 1];

    // make sure we do have a job
    if (job == NULL) {
        fprintf
            (stderr,
             "%s %s: job is still NULL ... this should never happen!",
             DEBUGFILE, DEBUGFUNCTION);
        exit(1);
    }
    // switch sf or mf
#ifdef USE_FFMPEG
    if (app->current_mode != 0)
        cto = &(app->multi_frame);
    else
#endif                          // USE_FFMPEG
        cto = &(app->single_frame);
    // various manual settings
    // need to have the flags set to smth. before other functions try to
    // do
    // flags |= or smth.
    job->flags = app->flags;

    // get area to capture
    job->area = xvc_get_capture_area();

    job_set_fps(cto->fps);

    job->state = VC_STOP;       // FIXME: better move this outta here?
    job->start_no = cto->start_no;
    job->pic_no = job->start_no;
    job->movie_no = 0;          // FIXME: make this configurable
    job->step = cto->step;
    job->max_frames = cto->frames;
    job->max_time = cto->time;
    job->quality = cto->quality;
    job->rescale = app->rescale;
    job->bpp = cto->bpp;
#ifdef HAVE_FFMPEG_AUDIO
    if (cto->audioWanted != 0)
        job->flags |= FLG_REC_SOUND;
    else
#endif                          // HAVE_FFMPEG_AUDIO
        job->flags &= ~FLG_REC_SOUND;
#ifdef HAVE_FFMPEG_AUDIO
    job_set_sound_dev(app->snddev, cto->sndrate, cto->sndsize,
                      cto->sndchannels);
#endif                          // HAVE_FFMPEG_AUDIO
    job->mouseWanted = app->mouseWanted;
    job->video_dev = app->device;

    job->get_colors = (void *(*)(XColor *, int)) NULL;
    job->clean = (void (*)(Job *)) NULL;

#ifdef DEBUG
    printf("%s %s: target is set to %i \n", DEBUGFILE, DEBUGFUNCTION,
           job->target);
#endif                          // DEBUG

    job->target = cto->target;
    if (job->target <= 0) {
        if (job->target == 0) {
            // we should be able to safely assume cto->filename is longer
            // smaller than 0 for the next bit because
            // with target == 0 it would have been set to a default value
            // otherwise
            job->target = xvc_codec_get_target_from_filename(cto->file);
            // we assume job->target can never be == 0 now, because a
            // sanity checking function
            // should have checked before if we have a valid specification 
            // 
            // 
            // for a target either
            // through target itself or the filename extension
            if (job->target <= 0) {
                fprintf(stderr,
                        "%s %s: Unrecoverable error while initializing job from app_data.\n",
                        DEBUGFILE, DEBUGFUNCTION);
                fprintf(stderr,
                        "targetCodec is still 0. This should never happen.\n");
                fprintf(stderr,
                        "Please contact the xvidcap project team.\n");
#ifdef DEBUG
                xvc_job_dump();
#endif                          // DEBUG
                exit(1);
            }
        }
    }

    job->targetCodec = cto->targetCodec;
    if (job->targetCodec <= 0) {
        if (job->targetCodec == 0)
            job->targetCodec = tFFormats[job->target].def_vid_codec;
        if (job->targetCodec < 0) {
            fprintf(stderr,
                    "%s %s: Unrecoverable error while initializing job from app_data.\n",
                    DEBUGFILE, DEBUGFUNCTION);
            fprintf(stderr,
                    "targetCodec is still < 0. This should never happen.\n");
            fprintf(stderr, "Please contact the xvidcap project team.\n");
#ifdef DEBUG
            xvc_job_dump();
#endif                          // DEBUG
            exit(1);
        }
    }
#ifdef HAVE_FFMPEG_AUDIO
    job->au_targetCodec = cto->au_targetCodec;
    if (job->au_targetCodec <= 0) {
        if (job->au_targetCodec == 0)
            job->au_targetCodec = tFFormats[job->target].def_au_codec;
        // if 0 the format has no default audio codec. This should only be 
        // 
        // 
        // 
        // the case if the format does not support audio or recording
        // without
        // audio is encouraged
        if (job->au_targetCodec < 0) {
            fprintf(stderr,
                    "%s %s: Unrecoverable error while initializing job from app_data.\n",
                    DEBUGFILE, DEBUGFUNCTION);
            fprintf(stderr,
                    "au_targetCodec is still < 0. This should never happen.\n");
            fprintf(stderr, "Please contact the xvidcap project team.\n");
#ifdef DEBUG
            xvc_job_dump();
#endif                          // DEBUG
            exit(1);
        }
    }
#endif                          // HAVE_FFMPEG_AUDIO

    job->color_table = NULL;
    job->colors = NULL;
    job->win_attr = wa;

    // the order of the following actions is key!
    // the default is to use the colormap of the root window
    job->ncolors = xvc_get_colors(disp, &job->win_attr, &job->colors);
    job_set_capture();

    // set temporary filename if set to "ask-user"
    if (strlen(cto->file) < 1) {
        char *home = NULL;
        int pid;

        home = getenv("HOME");
        pid = (int) getpid();

        snprintf(file, (PATH_MAX + 1), "%s/.xvidcap-tmp.%i.%s", home, pid,
                 xvc_next_element(tFFormats[job->target].extensions));
    } else {
        snprintf(file, (PATH_MAX + 1), "%s", cto->file);
    }

    job_set_file(file);
    xvc_job_set_save_function(job->win_attr.visual, job->target);

    // get color table
    if (job->get_colors)
        job->color_table = (*job->get_colors) (job->colors, job->ncolors);

#ifdef DEBUG
    printf("%s %s: Leaving function with this job:\n", DEBUGFILE,
           DEBUGFUNCTION);
    xvc_job_dump();
#endif                          // DEBUG
}


/* 
 * get a pointer to the static Job
 *
 */
Job *xvc_job_ptr(void)
{
#undef DEBUGFUNCTION
#define DEBUGFUNCTION "xvc_job_ptr()"

    return (job);
}


/* 
 * Select Function to use for processing the captured frame
 */
void xvc_job_set_save_function(Visual * vis, int type)
{
#undef DEBUGFUNCTION
#define DEBUGFUNCTION "xvc_job_set_save_function()"

#ifdef DEBUG2
    printf("%s %s: entering with type: %i\n", DEBUGFILE, DEBUGFUNCTION,
           type);
#endif                          // DEBUG2

#ifdef USE_FFMPEG
    if (type >= CAP_MF) {
        job->clean = FFMPEGClean;
        if (job->targetCodec == CODEC_NONE) {
            job->targetCodec = CODEC_MPEG1;
        }
        job->flags |= FLG_MULTI_IMAGE;
        job->get_colors = FFMPEGcolorTable;
        job->save = XImageToFFMPEG;
    } else if (type >= CAP_FFM) {
        job->clean = FFMPEGClean;
        if (job->targetCodec == CODEC_NONE) {
            job->targetCodec = CODEC_PGM;
        }
        job->flags &= ~FLG_MULTI_IMAGE;
        job->get_colors = FFMPEGcolorTable;
        job->save = XImageToFFMPEG;
    } else
#endif                          // USE_FFMPEG
    {
        job->save = XImageToXWD;
        job->get_colors = XWDcolorTable;
        job->flags &= ~FLG_MULTI_IMAGE;
        job->clean = NULL;
    }

}


/* 
 */
void job_set_fps(int fps)
{
#undef DEBUGFUNCTION
#define DEBUGFUNCTION "job_set_fps()"

#ifdef DEBUG2
    printf("%s %s: entering with fps: %i\n", DEBUGFILE, DEBUGFUNCTION,
           fps);
#endif                          // DEBUG2

    job->time_per_frame = (int) (1000 / (fps / (float) 100));
    job->fps = fps;
}


#ifdef HAVE_FFMPEG_AUDIO
/* 
 * set and check some parameters for the sound device
 */

int job_set_sound_dev(char *snd, int rate, int size, int channels)
{
#undef DEBUGFUNCTION
#define DEBUGFUNCTION "job_set_sound_dev()"
    extern int errno;
    struct stat statbuf;
    int stat_ret;

    job->snd_rate = rate;
    job->snd_smplsize = size;
    job->snd_channels = channels;
    job->snd_device = snd;

    if (job->flags & FLG_REC_SOUND) {
        if (strcmp(snd, "-") != 0) {
            stat_ret = stat(snd, &statbuf);

            if (stat_ret != 0) {
                switch (errno) {
                case EACCES:
                    fprintf(stderr,
                            _
                            ("Insufficient permission to access sound input from %s\n"),
                            snd);
                    fprintf(stderr, _("Sound disabled!\n"));
                    job->flags &= ~FLG_REC_SOUND;
                    break;
                default:
                    fprintf(stderr,
                            _("Error accessing sound input from %s\n"),
                            snd);
                    fprintf(stderr, _("Sound disabled!\n"));
                    job->flags &= ~FLG_REC_SOUND;
                    break;
                }
            }
        }
    }

    return 0;
}
#endif                          // HAVE_FFMPEG_AUDIO



/* 
 */
void job_set_file(char *file)
{
#undef DEBUGFUNCTION
#define DEBUGFUNCTION "job_set_file()"

    if (!file)
        return;

    job->file = strdup(file);

    sprintf(job->open_flags, "wb");

#ifdef DEBUG2
    printf("%s %s: leaving function with file = %s\n", DEBUGFILE,
           DEBUGFUNCTION, job->file);
#endif                          // DEBUG2
}


/* 
 * find the correct capture function
 */
void job_set_capture(void)
{
#undef DEBUGFUNCTION
#define DEBUGFUNCTION "job_set_capture()"
    int input = job->flags & FLG_SOURCE;

    switch (input) {
#ifdef HAVE_SHMAT
    case FLG_USE_SHM:
        job->capture = TCbCaptureSHM;
        break;
#endif                          // HAVE_SHMAT
    case FLG_USE_DGA:
        job->capture = TCbCaptureDGA;
        break;
#ifdef HasBTTV
    case FLG_USE_V4L:
        job->capture = TCbCaptureV4L;
        break;
#endif
    default:
        job->capture = TCbCaptureX11;
        break;
    }

}


/* 
 */
void job_set_quality(int quality)
{
#undef DEBUGFUNCTION
#define DEBUGFUNCTION "job_set_quality()"

    if (quality < 1)
        quality = 1;
    else if (quality > 100)
        quality = 100;
    job->quality = quality;
}


/* 
 * dump job settings
 */
void xvc_job_dump()
{
#undef DEBUGFUNCTION
#define DEBUGFUNCTION "xvc_job_dump()"

    printf("fps = %.2f\n", (float) (job->fps / 100.00));
    printf("file = %s\n", job->file);
    printf("flags = %i\n", job->flags);
    printf("state = %i\n", job->state);
    printf("start_no = %i\n", job->start_no);
    printf("pic_no = %i\n", job->pic_no);
    printf("movie_no = %i\n", job->movie_no);
    printf("step = %i\n", job->step);
    printf("time_per_frame = %i\n", job->time_per_frame);
    printf("max_frames = %i\n", job->max_frames);
    printf("max_time = %i\n", job->max_time);
    printf("quality = %i\n", job->quality);
    printf("open_flags = %s\n", strdup(job->open_flags));
    printf("bpp = %i\n", job->bpp);
    printf("vid_dev = %i\n", job->vid_dev);
#ifdef HAVE_FFMPEG_AUDIO
    printf("snd_dev = %i\n", job->snd_dev);
    printf("snd_rate = %i\n", job->snd_rate);
    printf("snd_smplsize = %i\n", job->snd_smplsize);
    printf("snd_channels = %i\n", job->snd_channels);
#endif                          // HAVE_FFMPEG_AUDIO
    printf("mouseWanted = %i\n", job->mouseWanted);
    printf("video_dev = %s\n", job->video_dev);
#ifdef HAVE_FFMPEG_AUDIO
    printf("snd_device = %s\n", job->snd_device);
#endif                          // HAVE_FFMPEG_AUDIO

    printf("get_colors = %p\n", job->get_colors);
    printf("save = %p\n", job->save);
    printf("clean = %p\n", job->clean);
    printf("capture = %p\n", job->capture);

    printf("target = %i\n", job->target);
    printf("targetCodec = %i\n", job->targetCodec);
    printf("ncolors = %i\n", job->ncolors);

    printf("color_table = %p\n", job->color_table);
    printf("colors = %p\n", job->colors);
    printf("win_attr (w/h/x/y) = %i/%i/%i/%i\n", job->win_attr.width,
           job->win_attr.height, job->win_attr.x, job->win_attr.y);
    printf("area (w/h/x/y) = %i/%i/%i/%i\n", job->area->width,
           job->area->height, job->area->x, job->area->y);

}


/* 
 * validate job
 * some things, can only be set after the app_data things have become a job
 * these things include e.g. flags and capture size
 */
void xvc_job_validate()
{
#undef DEBUGFUNCTION
#define DEBUGFUNCTION "xvc_job_validate()"

    // unset autocontinue unless we capture to movie and file is mutable 
    if (job->flags & FLG_AUTO_CONTINUE &&
        ((!xvc_is_filename_mutable(job->file)) ||
         (job->flags & FLG_MULTI_IMAGE) == 0)) {
        job->flags &= ~FLG_AUTO_CONTINUE;
        printf
            ("Output not a video file or no counter in filename\nDisabling autocontinue!\n");
    }
#ifdef USE_FFMPEG
    // make sure we have even width and height for ffmpeg 
    if (job->target >= CAP_MF) {
        Boolean changed = FALSE;
        int orig_width = job->area->width, orig_height = job->area->height;

        if ((job->area->width % 2) > 0) {
            job->area->width--;
            changed = TRUE;
        }
        if ((job->area->height % 2) > 0) {
            job->area->height--;
            changed = TRUE;
        }
        if (job->win_attr.width < 26) {
            job->win_attr.width = 26;
            changed = TRUE;
        }
        if (job->win_attr.height < 26) {
            job->win_attr.height = 26;
            changed = TRUE;
        }

        if (changed) {
            xvc_frame_change(job->area->x, job->area->y, job->area->width,
                             job->area->height, FALSE);
            if (job->flags & FLG_RUN_VERBOSE) {
                fprintf(stdout, _("%s %s: Original dimensions: %i * %i\n"),
                        DEBUGFILE, DEBUGFUNCTION, orig_width, orig_height);
                fprintf(stdout,
                        _("%s %s(): Modified dimensions: %i * %i\n"),
                        DEBUGFILE, DEBUGFUNCTION, job->area->width,
                        job->area->height);
            }
        }

    }
#endif                          // USE_FFMPEG

}


void job_state_change_signals_thread(int orig_state, int new_state)
{
    if (((orig_state & VC_PAUSE) > 0 && (new_state & VC_PAUSE) == 0) ||
        ((orig_state & VC_STOP) == 0 && (new_state & VC_STOP) > 0) ||
        ((orig_state & VC_STEP) == 0 && (new_state & VC_STEP) > 0)
        ) {
        // signal potentially paused thread
        pthread_cond_broadcast(&recording_condition_unpaused);
    }
}

void job_set_state(int state)
{
    int orig_state = job->state;

    pthread_mutex_lock(&recording_mutex);
    job->state = state;
    job_state_change_signals_thread(orig_state, job->state);
    pthread_mutex_unlock(&recording_mutex);
}

void job_merge_state(int state)
{
    int orig_state = job->state;

    pthread_mutex_lock(&recording_mutex);
    job->state |= state;
    job_state_change_signals_thread(orig_state, job->state);
    pthread_mutex_unlock(&recording_mutex);
}

void job_remove_state(int state)
{
    int orig_state = job->state;

    pthread_mutex_lock(&recording_mutex);
    job->state &= ~(state);
    job_state_change_signals_thread(orig_state, job->state);
    pthread_mutex_unlock(&recording_mutex);
}

void job_merge_and_remove_state(int merge_state, int remove_state)
{
    int orig_state = job->state;

    pthread_mutex_lock(&recording_mutex);
    job->state |= merge_state;
    job->state &= ~(remove_state);
    job_state_change_signals_thread(orig_state, job->state);
    pthread_mutex_unlock(&recording_mutex);
}

void job_keep_state(int state)
{
    int orig_state = job->state;

    pthread_mutex_lock(&recording_mutex);
    job->state &= state;
    job_state_change_signals_thread(orig_state, job->state);
    pthread_mutex_unlock(&recording_mutex);
}

void job_keep_and_merge_state(int keep_state, int merge_state)
{
    int orig_state = job->state;

    pthread_mutex_lock(&recording_mutex);
    job->state &= keep_state;
    job->state |= merge_state;
    job_state_change_signals_thread(orig_state, job->state);
    pthread_mutex_unlock(&recording_mutex);
}


syntax highlighted by Code2HTML, v. 0.9.1