/*
 *  Copyright 1994-2022 Olivier Girondel
 *
 *  This file is part of lebiniou.
 *
 *  lebiniou 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.
 *
 *  lebiniou 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 lebiniou. If not, see <http://www.gnu.org/licenses/>.
 */

#include "context.h"

uint32_t options = BO_NONE;
uint32_t version = 0;
char desc[] = "MP4 video encoder";

#define FFMPEG_CHECK         "ffmpeg -h >/dev/null 2>&1"
#define MP4_FFMPEG_CMD       "ffmpeg -y -loglevel quiet -bitexact -framerate %d -vcodec ppm -f image2pipe -i pipe: -vcodec libx264 -crf %s -pix_fmt yuv420p \"%s\""
#define MP4_FFMPEG_CMD_AUDIO "ffmpeg -y -loglevel quiet -bitexact -framerate %d -vcodec ppm -f image2pipe -i pipe: -i \"%s\" -c:a libmp3lame -b:a %s -vcodec libx264 -crf %s -pix_fmt yuv420p \"%s\""


#define DIRECTORY      "/mp4/"

static gchar *mp4_filename = NULL;
static FILE *mp4 = NULL;
extern uint8_t max_fps;
extern uint8_t encoding;
extern char *audio_file;


static gchar *
video_filename(void)
{
  gchar *blah = NULL;
  time_t s;
  struct tm *now;

  s = time(NULL);
  now = localtime(&s);
  blah = g_strdup_printf("%s/.%s/%s", g_get_home_dir(), PACKAGE_NAME, DIRECTORY);
  g_mkdir_with_parents(blah, DIRECTORY_MODE);
  g_free(blah);

  if (NULL == audio_file) {
    audio_file = getenv("LEBINIOU_SNDFILE");
  }
  if (NULL != audio_file) {
    char *c;
    audio_file = (NULL != (c = strrchr(audio_file, '/'))) ? ++c : audio_file;
    (NULL != (c = strrchr(audio_file, '.'))) && (*c = '\0'); /* spr0tch */
  }
  blah = g_strdup_printf("%s/." PACKAGE_NAME DIRECTORY "%s-%04d-%02d-%02d_%02d-%02d-%02d.mp4",
                         g_get_home_dir(), (NULL != audio_file) ? audio_file : PACKAGE_NAME,
                         now->tm_year + 1900, now->tm_mon + 1, now->tm_mday,
                         now->tm_hour, now->tm_min, now->tm_sec);
  VERBOSE(printf("[i] %s: encoding video to %s\n", __FILE__, blah));

  return blah;
}


static int
open_mp4(Context_t *ctx)
{
  if (NULL == audio_file) {
    audio_file = getenv("LEBINIOU_SNDFILE");
  }
  char *mp4_crf = getenv("LEBINIOU_MP4_CRF");
  if (NULL == mp4_crf) {
    // use ffmpeg default
    mp4_crf = "23";
  }
  char *audio_encoding_rate = getenv("LEBINIOU_MP4_AUDIO_ENCODING_RATE");
  if (NULL == audio_encoding_rate) {
    // use ffmpeg default
    audio_encoding_rate = "128k";
  }
  // strdup() is needed because LEBINIOU_SNDFILE gets modified in video_filename()
  char *audio = (NULL != audio_file) ? strdup(audio_file) : NULL;
  char *env = getenv("LEBINIOU_MP4_FILENAME");
  // strdup() is needed because mp4_filename is g_freed()
  mp4_filename = (NULL != env) ? g_strdup(env) : video_filename();
  gchar *cmd;

  if (NULL != audio) {
    cmd = g_strdup_printf(MP4_FFMPEG_CMD_AUDIO, max_fps, audio, audio_encoding_rate, mp4_crf, mp4_filename);
#ifdef DEBUG
    fprintf(stderr, "%s:%s cmd= '%s'\n", __FILE__, __func__, cmd);
#endif
    xfree(audio);
  } else {
    cmd = g_strdup_printf(MP4_FFMPEG_CMD, max_fps, mp4_crf, mp4_filename);
  }

  if (NULL == (mp4 = popen(cmd, "w"))) {
    xperror("popen");
  } else {
    VERBOSE(printf("[i] %s: cmd= %s\n", __FILE__, cmd));
  }
  g_free(cmd);

  return 1;
}


int8_t
create(Context_t *ctx)
{
  if (check_command(FFMPEG_CHECK) == -1) {
    printf("[!] %s: ffmpeg binary not found, can't create video\n", __FILE__);

    return 0;
  }

  if (encoding) {
    return open_mp4(ctx);
  }

  return 1;
}


void
destroy(Context_t *ctx)
{
  if (NULL != mp4)
    if (-1 == pclose(mp4)) {
      fprintf(stderr, "[!] ");
      perror("pclose");
    }
  if (NULL != mp4_filename) {
    g_free(mp4_filename);
  }
}


void
run(Context_t *ctx)
{
  if (NULL != mp4) {
    uint8_t *data;
    char buff[MAXLEN+1];
    size_t res;

    /* get picture */
    data = export_RGB_active_buffer(ctx, 1);

    memset(&buff, '\0', MAXLEN+1);
    g_snprintf(buff, MAXLEN, "P6  %d %d 255\n", WIDTH, HEIGHT);

    /* PPM header */
    res = fwrite((const void *)&buff, sizeof(char), strlen(buff), mp4);
    if (res != strlen(buff)) {
      fprintf(stderr, "[!] %s:write_header: short write (%zu of %d)\n", __FILE__, res, (int)strlen(buff));
      exit(1);
    }

    /* PPM data */
    res = fwrite((const void *)data, sizeof(Pixel_t), RGB_BUFFSIZE, mp4);
    xfree(data);
    if (res != RGB_BUFFSIZE) {
      fprintf(stderr, "[!] %s:write_image: short write (%zu of %li)\n", __FILE__, res, RGB_BUFFSIZE);
      exit(1);
    }

    fflush(mp4);

    // dump screenshot for reproducibility test
    if (NULL != getenv("LEBINIOU_DUMP_FRAMES")) {
      Context_screenshot(ctx, 1);
    }
  }
}


json_t *
command(Context_t *ctx, const json_t *cmd)
{
  json_t *res = NULL;

  if (is_equal(json_string_value(cmd), "start_encoding")) {
    if (NULL == mp4) {
      encoding = 1;
      open_mp4(ctx);
      res = json_pack("{sb}", "encoding", encoding);
    } else {
      res = json_pack("{ss}", "error", "encoding in progress");
    }
  } else if (is_equal(json_string_value(cmd), "stop_encoding")) {
    if (NULL != mp4) {
      encoding = 0;
      if (-1 == pclose(mp4)) {
        xperror("pclose");
      }
      mp4 = NULL;
      res = json_pack("{sb}", "encoding", encoding);
      g_free(mp4_filename);
      mp4_filename = NULL;
    } else {
      res = json_pack("{ss}", "error", "not encoding");
    }
  }

  return res;
}
