/*
Looking Glass - KVM FrameRelay (KVMFR) Client
Copyright (C) 2017-2019 Geoffrey McRae <geoff@hostfission.com>
https://looking-glass.hostfission.com

This program is free software; you can redistribute it and/or modify it under
cahe 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., 59 Temple
Place, Suite 330, Boston, MA 02111-1307 USA
*/

#include "espobject.h"
#include "common/debug.h"
#include "common/locking.h"

#include "texture.h"
#include "shader.h"
#include "model.h"

#include <stdlib.h>
#include <string.h>

// these headers are auto generated by cmake
#include "espobject.vert.h"
#include "espobject.frag.h"
#include "espobject_bg.frag.h"

struct EGL_ESPObject
{
  const LG_Font * font;
  LG_FontObj      fontObj;

  EGL_Texture * texture;
  EGL_Shader  * shader;
  EGL_Shader  * shaderBG;
  EGL_Model   * model;

  LG_Lock         lock;
  bool            update;
  LG_FontBitmap * bmp;

  bool     ready;
  float    width  , height  ;
  float    bgWidth, bgHeight;
  float    r, g, b, a;
  float    x, y, distance;

  // uniforms
  GLint uScreen  , uSize;
  GLint uScreenBG, uSizeBG, uColorBG;
  GLint uOffsetBG, uOffset;
};

bool egl_espobject_init(EGL_ESPObject ** espobject, const LG_Font * font, LG_FontObj fontObj)
{
  *espobject = (EGL_ESPObject *)malloc(sizeof(EGL_ESPObject));
  if (!*espobject)
  {
    DEBUG_ERROR("Failed to malloc EGL_ESPObject");
    return false;
  }

  memset(*espobject, 0, sizeof(EGL_ESPObject));

  (*espobject)->font    = font;
  (*espobject)->fontObj = fontObj;
  LG_LOCK_INIT((*espobject)->lock);

  if (!egl_texture_init(&(*espobject)->texture))
  {
    DEBUG_ERROR("Failed to initialize the espobject texture");
    return false;
  }

  if (!egl_shader_init(&(*espobject)->shader))
  {
    DEBUG_ERROR("Failed to initialize the espobject shader");
    return false;
  }

  if (!egl_shader_init(&(*espobject)->shaderBG))
  {
    DEBUG_ERROR("Failed to initialize the espobject bg shader");
    return false;
  }


  if (!egl_shader_compile((*espobject)->shader,
        b_shader_espobject_vert, b_shader_espobject_vert_size,
        b_shader_espobject_frag, b_shader_espobject_frag_size))
  {
    DEBUG_ERROR("Failed to compile the espobject shader");
    return false;
  }

  if (!egl_shader_compile((*espobject)->shaderBG,
        b_shader_espobject_vert   , b_shader_espobject_vert_size,
        b_shader_espobject_bg_frag, b_shader_espobject_bg_frag_size))
  {
    DEBUG_ERROR("Failed to compile the espobject shader");
    return false;
  }


  (*espobject)->uSize     = egl_shader_get_uniform_location((*espobject)->shader  , "size"      );
  (*espobject)->uScreen   = egl_shader_get_uniform_location((*espobject)->shader  , "screen"    );
  (*espobject)->uSizeBG   = egl_shader_get_uniform_location((*espobject)->shaderBG, "size"      );
  (*espobject)->uScreenBG = egl_shader_get_uniform_location((*espobject)->shaderBG, "screen"    );
  (*espobject)->uColorBG  = egl_shader_get_uniform_location((*espobject)->shaderBG, "color"     );

  (*espobject)->uOffsetBG  = egl_shader_get_uniform_location((*espobject)->shaderBG, "offset"   );
  (*espobject)->uOffset    = egl_shader_get_uniform_location((*espobject)->shader  , "offset"   );

  if (!egl_model_init(&(*espobject)->model))
  {
    DEBUG_ERROR("Failed to initialize the espobject model");
    return false;
  }

  egl_model_set_default((*espobject)->model);
  egl_model_set_texture((*espobject)->model, (*espobject)->texture);

  return true;
}

void egl_espobject_free(EGL_ESPObject ** espobject)
{
  if (!*espobject)
    return;

  egl_texture_free(&(*espobject)->texture );
  egl_shader_free (&(*espobject)->shader  );
  egl_shader_free (&(*espobject)->shaderBG);
  egl_model_free  (&(*espobject)->model   );

  free(*espobject);
  *espobject = NULL;
}

void egl_espobject_set_color(EGL_ESPObject * espobject, const uint32_t color)
{
  espobject->r = (1.0f / 0xff) * ((color >> 24) & 0xFF);
  espobject->g = (1.0f / 0xff) * ((color >> 16) & 0xFF);
  espobject->b = (1.0f / 0xff) * ((color >>  8) & 0xFF);
  espobject->a = 0.1;
}

void egl_espobject_set_text (EGL_ESPObject * espobject, const char * str)
{
  LG_LOCK(espobject->lock);
  espobject->bmp = espobject->font->render(espobject->fontObj, 0xffffff00, str);
  if (!espobject->bmp)
  {
    espobject->update = false;
    LG_UNLOCK(espobject->lock);
    DEBUG_ERROR("Failed to render espobject text");
    return;
  }

  espobject->update = true;
  LG_UNLOCK(espobject->lock);
}

void egl_espobject_set_screenpos (EGL_ESPObject * espobject, float x, float y, float distance)
{
  espobject->x = x;
  espobject->y = y;
  espobject->distance = distance;
}

void egl_espobject_render(EGL_ESPObject * espobject, const float scaleX, const float scaleY)
{
  if (espobject->update)
  {
    LG_LOCK(espobject->lock);
    egl_texture_setup(
      espobject->texture,
      EGL_PF_BGRA,
      espobject->bmp->width ,
      espobject->bmp->height,
      espobject->bmp->width * espobject->bmp->bpp,
      false
    );

    egl_texture_update(espobject->texture, espobject->bmp->pixels);

    espobject->width  = espobject->bgWidth  = espobject->bmp->width;
    espobject->height = espobject->bgHeight = espobject->bmp->height;

    espobject->ready  = true;

    espobject->font->release(espobject->fontObj, espobject->bmp);
    espobject->update = false;
    espobject->bmp    = NULL;
    LG_UNLOCK(espobject->lock);
  }

  if (!espobject->ready)
    return;

  glEnable(GL_BLEND);
  glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

  // render the background first
  egl_shader_use(espobject->shaderBG);
  glUniform2f(espobject->uScreenBG, scaleX        , scaleY         );
  glUniform2i(espobject->uSizeBG  , espobject->bgWidth, espobject->bgHeight);
  glUniform2f(espobject->uOffsetBG  , espobject->x, espobject->y);
  glUniform4f(espobject->uColorBG , espobject->r, espobject->g, espobject->b, espobject->a);
  egl_model_render(espobject->model);

  // render the texture over the background
  egl_shader_use(espobject->shader);
  glUniform2f(espobject->uScreen, scaleX      , scaleY       );
  glUniform2i(espobject->uSize  , espobject->width, espobject->height);
  glUniform2f(espobject->uOffset  , espobject->x, espobject->y);
  egl_model_render(espobject->model);

  glDisable(GL_BLEND);
}
