/* BSD 3-Clause License
 *
 * Copyright © 2008-2020, Jice and the libtcod contributors.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * 1. Redistributions of source code must retain the above copyright notice,
 *    this list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright notice,
 *    this list of conditions and the following disclaimer in the documentation
 *    and/or other materials provided with the distribution.
 *
 * 3. Neither the name of the copyright holder nor the names of its
 *    contributors may be used to endorse or promote products derived from
 *    this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */
#ifndef NO_SDL
#include "renderer_gl.h"

#include <SDL.h>

#include "../vendor/glad.h"
#include "error.h"
/**
 *  Return a rectangle shaped for a tile at `x`,`y`.
 */
static SDL_Rect get_aligned_tile(const struct TCOD_Tileset* tileset, int x, int y) {
  SDL_Rect tile_rect = {x * tileset->tile_width, y * tileset->tile_height, tileset->tile_width, tileset->tile_height};
  return tile_rect;
}
/**
 *  Return the rectangle for the tile at `tile_id`.
 */
static SDL_Rect get_gl_atlas_tile(const struct TCOD_TilesetAtlasOpenGL* atlas, int tile_id) {
  return get_aligned_tile(atlas->tileset, tile_id % atlas->texture_columns, tile_id / atlas->texture_columns);
}
/**
 *  Upload a single tile to the atlas texture.
 */
static int upload_gl_tile(struct TCOD_TilesetAtlasOpenGL* atlas, int tile_id) {
  SDL_Rect dest = get_gl_atlas_tile(atlas, tile_id);
  glBindTexture(GL_TEXTURE_2D, atlas->texture);
  glTexSubImage2D(
      GL_TEXTURE_2D,
      0,
      dest.x,
      dest.y,
      dest.w,
      dest.h,
      GL_RGBA,
      GL_UNSIGNED_BYTE,
      atlas->tileset->pixels + (tile_id * atlas->tileset->tile_length));
  glBindTexture(GL_TEXTURE_2D, 0);
  return 0;
}
/**
 *  Setup a atlas texture and upload the tileset graphics.
 */
TCOD_NODISCARD
static int prepare_gl_atlas(struct TCOD_TilesetAtlasOpenGL* atlas) {
  int new_size = atlas->texture_size ? atlas->texture_size : 64;
  int max_size;
  glGetIntegerv(GL_MAX_TEXTURE_SIZE, &max_size);
  int rows;
  int columns;
  while (1) {
    rows = new_size / atlas->tileset->tile_width;
    columns = new_size / atlas->tileset->tile_height;
    if (rows * columns >= atlas->tileset->tiles_capacity) {
      break;
    }
    new_size *= 2;
  }
  if (new_size > max_size) {
    TCOD_set_errorv("Tried to allocate a texture size above the maximum limit!");
    return TCOD_E_ERROR;
  }
  if (new_size != atlas->texture_size) {
    atlas->texture_size = new_size;
    atlas->texture_columns = columns;
    atlas->texture_rows = rows;
    glBindTexture(GL_TEXTURE_2D, atlas->texture);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
    glTexImage2D(
        GL_TEXTURE_2D, 0, GL_RGBA, atlas->texture_size, atlas->texture_size, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);
    glBindTexture(GL_TEXTURE_2D, 0);
    switch (glGetError()) {
      case GL_NO_ERROR:
        break;
      case GL_OUT_OF_MEMORY:
        TCOD_set_errorv("Out of memory while allocating texture.");
        return TCOD_E_OUT_OF_MEMORY;
      default:
        TCOD_set_errorv("Unkown OpenGL error while allocating texture.");
        return TCOD_E_ERROR;
    }
    for (int i = 0; i < atlas->tileset->tiles_count; ++i) {
      if (upload_gl_tile(atlas, i) < 0) {
        return -1;
      }
    }
    return 1;  // Atlas texture has been resized and refreshed.
  }
  return 0;  // No action.
}
/**
 *  Respond to changes in a tileset.
 */
static int gl_atlas_on_tile_changed(struct TCOD_TilesetObserver* observer, int tile_id) {
  struct TCOD_TilesetAtlasOpenGL* atlas = observer->userdata;
  if (prepare_gl_atlas(atlas) == 1) {
    return 0;  // Tile updated as a side-effect of prepare_gl_atlas.
  }
  return upload_gl_tile(atlas, tile_id);
}
struct TCOD_TilesetAtlasOpenGL* TCOD_gl_atlas_new(struct TCOD_Tileset* tileset) {
  if (!tileset) {
    return NULL;
  }
  struct TCOD_TilesetAtlasOpenGL* atlas = calloc(sizeof(*atlas), 1);
  if (!atlas) {
    return NULL;
  }
  atlas->observer = TCOD_tileset_observer_new(tileset);
  if (!atlas->observer) {
    TCOD_gl_atlas_delete(atlas);
    return NULL;
  }
  atlas->tileset = tileset;
  atlas->tileset->ref_count += 1;
  atlas->observer->userdata = atlas;
  atlas->observer->on_tile_changed = gl_atlas_on_tile_changed;
  glGenTextures(1, &atlas->texture);
  if (prepare_gl_atlas(atlas) < 0) {
    TCOD_gl_atlas_delete(atlas);
    return NULL;
  }
  return atlas;
}
void TCOD_gl_atlas_delete(struct TCOD_TilesetAtlasOpenGL* atlas) {
  if (!atlas) {
    return;
  }
  if (atlas->tileset) {
    TCOD_tileset_delete(atlas->tileset);
  }
  if (atlas->texture) {
    glDeleteTextures(1, &atlas->texture);
  }
  free(atlas);
}
#endif  // NO_SDL
