/*
 *  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/>.
 */

#ifdef WITH_GL
#include <GL/gl.h>
#include <GL/glu.h>
#endif
#include "biniou.h"
#include "schemes.h"
#include "images.h"
#include "colormaps.h"
#include "brandom.h"
#include "pthread_utils.h"


#define BARSIZE (HEIGHT/20) // Colormap bar size

extern uint64_t frames;
extern uint8_t  max_fps;
extern uint8_t  encoding;


static void
Context_boundary(Context_t *ctx)
{
  switch (ctx->params3d.boundary) {
    case BOUNDARY_CUBE:
      draw_cube_3d(&ctx->params3d, active_buffer(ctx), PIXEL_MAXVAL);
      break;

    case BOUNDARY_SPHERE_DOTS:
      draw_sphere_3d(&ctx->params3d, active_buffer(ctx), PIXEL_MAXVAL);
      break;

    case BOUNDARY_SPHERE_WIREFRAME:
      draw_sphere_wireframe_3d(&ctx->params3d, active_buffer(ctx), PIXEL_MAXVAL);
      break;

    case BOUNDARY_NONE:
    default:
      break;
  }
}


static void
Context_sync(Context_t *ctx)
{
  uint8_t i;
  const float rrt = Timer_elapsed(ctx->fps_timer);
  const float sleep = ctx->i_max_fps - rrt;

  for (i = 0; i < NFPS - 1; i++) {
    ctx->fps[i] = ctx->fps[i + 1];
  }
  ctx->fps[i] = (rrt > 0) ? (1.0 / rrt) : 0.0;

  if (!encoding && (sleep > 0)) {
    ms_sleep(sleep * MFACTOR);
  }

  Timer_start(ctx->fps_timer);
}


static void
Context_sequence(Context_t *ctx)
{
  const GList *tmp;
  uint16_t count = 0;

  tmp = ctx->sm->cur->layers;

#ifdef WITH_GL
  ctx->texture_ready = ctx->texture_used = ctx->gl_done = 0;
#endif

  while (NULL != tmp) {
    Layer_t *layer = (Layer_t *)tmp->data;
    Plugin_t *P = layer->plugin;

    assert(NULL != P);
    if (P == ctx->sm->cur->lens) {
      push_buffer(ctx);
    }

    if (NULL != P->run) {
      if (ctx->bypass && (*P->options & BO_WEBCAM)) { // bypass webcam plugin
        Buffer8_XOR(active_buffer(ctx), ctx->target_pic->buff);
      } else {
        P->run(ctx);
      }
      P->calls++;
      count++;
    }

    switch (layer->mode) {
    case LM_NONE:
      break;

    case LM_NORMAL:
      swap_buffers(ctx);
      break;

    case LM_OVERLAY:
      Buffer8_overlay(active_buffer(ctx), passive_buffer(ctx));
      break;

    case LM_AND:
      Buffer8_AND(active_buffer(ctx), passive_buffer(ctx));
      break;

    case LM_OR:
      Buffer8_OR(active_buffer(ctx), passive_buffer(ctx));
      break;

    case LM_XOR:
      Buffer8_XOR(active_buffer(ctx), passive_buffer(ctx));
      break;

    case LM_AVERAGE:
      Buffer8_average(active_buffer(ctx), passive_buffer(ctx));
      break;

    case LM_INTERLEAVE:
      Context_interleave_buffers(ctx);
      break;

    case LM_RANDOM: {
      Buffer8_t *buffs[2] = { active_buffer(ctx), passive_buffer(ctx) };
      Context_mix_buffers(ctx, buffs);
    }
      break;

    case LM_BANDPASS:
      Buffer8_bandpass(active_buffer(ctx), passive_buffer(ctx), ctx->bandpass_min, ctx->bandpass_max);
      break;

    default:
      xerror("Unsupported layer mode %d\n", layer->mode);
      break;
    }

    tmp = g_list_next(tmp);
  }

  if (!count && ctx->random_mode) {
    assert(NULL != ctx->target_pic);
    Buffer8_copy(ctx->target_pic->buff, active_buffer(ctx));
  }
}


static void
Context_auto_change(Context_t *ctx)
{
  if (Alarm_ring(ctx->a_random)) {
    // Auto-change sequence
    switch (ctx->random_mode) {
    case BR_SCHEMES:
      if (NULL != schemes) {
        printf("[+] Random scheme\n");
        Schemes_random(ctx, 0);
      }
      break;

    case BR_SEQUENCES:
      if ((NULL != sequences) && sequences->size) {
        printf("[+] Random sequence\n");
        Context_random_sequence(ctx);
      }
      break;

    case BR_BOTH:
      if ((NULL != schemes) && (NULL != sequences) && sequences->size) {
        if (b_rand_boolean()) {
          printf("[+] Random scheme\n");
          Schemes_random(ctx, 0);
        } else {
          printf("[+] Random sequence\n");
          Context_random_sequence(ctx);
        }
      } else if ((NULL != sequences) && sequences->size) {
        printf("[+] Random sequence\n");
        Context_random_sequence(ctx);
      } else if (NULL != schemes) {
        printf("[+] Random scheme\n");
        Schemes_random(ctx, 0);
      }
      break;

    default:
      break;
    }
  }
}


void
Context_run(Context_t *ctx)
{
  Plugin_t *input = ctx->input_plugin;
  GSList *outputs = ctx->outputs;

  // sync fps
  Context_sync(ctx);

  if (!SequenceManager_lock(ctx->sm)) {
    if (NULL != ctx->playlist) {
      if (Timer_elapsed(ctx->track_timer) >= ctx->track_duration) {
        Context_next_track(ctx);
      }
    } else {
      if (ctx->allow_random_changes) {
        Context_auto_change(ctx);
      }
    }

    // Auto-change colormap
    // Timer elapsed ?
    if (ctx->cf->on && Alarm_ring(ctx->a_cmaps) && !ctx->lock_colormap && ctx->allow_auto_colormaps) {
      CmapFader_random(ctx->cf);
      Context_websocket_send_colormap(ctx);
    }
    // Fade colormap
    if (CmapFader_ring(ctx->cf)) {
      CmapFader_run(ctx->cf);
      ctx->sm->cur->cmap_id = ctx->cf->dst->id;
    }

    // Auto-change image
    if (NULL != ctx->imgf) {
      // Timer elapsed ?
      if (ctx->imgf->on && Alarm_ring(ctx->a_images) && !ctx->lock_image && ctx->allow_auto_images) {
        ImageFader_random(ctx->imgf);
        Context_websocket_send_image(ctx);
      }
      // Fade image
      if (ImageFader_ring(ctx->imgf)) {
        ImageFader_run(ctx->imgf);
        ctx->sm->cur->image_id = ctx->imgf->dst->id;
      }
    }

#ifdef WITH_WEBCAM
    // Auto-change webcam
    if ((ctx->webcams > 1) && ctx->auto_webcams && Alarm_ring(ctx->a_webcams) && !ctx->lock_webcam) {
      uint16_t cam_no = Shuffler_get(ctx->webcams_shuffler);
      ctx->cam = cam_no;
#ifdef DEBUG
      printf("[i] Using webcam #%d\n", cam_no);
#endif
    }
#endif

    /* If we have an input plugin that runs in non-threaded mode,
     * call it now */
    if ((NULL != input) && (NULL != input->run)) {
      input->run(ctx);
      input->calls++;
    }
    if (NULL != ctx->input) {
      Input_process(ctx->input);
    }

    // 3D rotations
    Params3d_change_rotations(&ctx->params3d);

#ifdef WITH_GL
    glClearColor(0, 0, 0, 0);
    glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);

    // Initialize projection matrix
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();

    gluPerspective(ctx->params3d.gl_fov, (float)WIDTH/(float)HEIGHT,
                   0.1, 10); //ZMAX);
    gluLookAt(0, 0, 5, //3.14,
              0, 0, -1, //-3.14,
              0, 1, 0);

    glShadeModel(GL_SMOOTH);

    glClearDepth(ZMAX);
    glEnable(GL_DEPTH_TEST);
    glDepthFunc(GL_LEQUAL);

    glEnable(GL_BLEND);

    glRotatef(ctx->params3d.gl_rotations[0], 1.0, 0.0, 0.0);
    glRotatef(ctx->params3d.gl_rotations[1], 0.0, 1.0, 0.0);
    glRotatef(ctx->params3d.gl_rotations[2], 0.0, 0.0, 1.0);

    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
    if (ctx->pulse_3d) {
      float scale = Input_get_volume(ctx->input) + 1.0;
      glScalef(scale, scale, scale);
    }
#endif

    // The sequence
    Context_sequence(ctx);

    // draw 3D boundary
    Context_boundary(ctx);

    // display the current colormap
    if (ctx->display_colormap) {
      if (NULL == ctx->sm->cur->lens) {
        push_buffer(ctx);
      }
      Buffer8_color_bar(active_buffer(ctx), BARSIZE);
    }

    // store current RGBA frame
    if (!xpthread_mutex_lock(&ctx->frame_mutex)) {
      xfree(ctx->frame);
      ctx->frame = export_RGB_active_buffer(ctx, 1);
      assert(NULL != ctx->frame);
      xpthread_mutex_unlock(&ctx->frame_mutex);
    }

    for ( ; NULL != outputs; outputs = g_slist_next(outputs)) {
      Plugin_t *output = (Plugin_t *)outputs->data;

      if (NULL != output->run) {
        output->run(ctx);
        output->calls++;
      }
    }

    // All outputs should have refreshed their cmap by now
    ctx->cf->refresh = 0;

    // Screenshot
    if (ctx->take_screenshot) {
      Context_screenshot(ctx, 0);
      ctx->take_screenshot = 0;
    }

    // Restore possibly saved buffer
    if ((NULL != ctx->sm->cur->lens) || ctx->display_colormap) {
      pop_buffer(ctx);
    }

    // Process commands
    Context_process_commands(ctx);

    SequenceManager_unlock(ctx->sm);
  }
  frames++;
}
