#include "renderer.h"
#include "game.h"

Renderer::Renderer()
{
  win_ = window_ptr(
    SDL_CreateWindow(
      "Ether",
      100,
      100,
      GAME_WIDTH,
      GAME_HEIGHT,
      SDL_WINDOW_SHOWN));

  if (!win_)
  {
    throw sdl_error();
  }

  ren_ = renderer_ptr(
    SDL_CreateRenderer(
      win_.get(),
      -1,
      SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC));

  if (!ren_)
  {
    throw sdl_error();
  }

  texture_ptr origFade;
  {
    surface_ptr pfs(IMG_Load("../res/lighting.png"));
    if (!pfs)
    {
      throw img_error();
    }

    origFade = texture_ptr(SDL_CreateTextureFromSurface(ren_.get(), pfs.get()));
  }

  SDL_SetTextureBlendMode(origFade.get(), SDL_BLENDMODE_BLEND);

  playerFade_ = texture_ptr(
    SDL_CreateTexture(
      ren_.get(),
      SDL_PIXELFORMAT_RGBA4444,
      SDL_TEXTUREACCESS_TARGET,
      144,
      144));

  if (!playerFade_)
  {
    throw sdl_error();
  }

  SDL_SetRenderTarget(ren_.get(), playerFade_.get());
  SDL_SetRenderDrawBlendMode(ren_.get(), SDL_BLENDMODE_NONE);
  SDL_SetRenderDrawColor(ren_.get(), 0, 0, 0, 0);
  SDL_RenderClear(ren_.get());
  SDL_RenderCopy(ren_.get(), origFade.get(), nullptr, nullptr);

  lampFade_ = texture_ptr(
    SDL_CreateTexture(
      ren_.get(),
      SDL_PIXELFORMAT_RGBA4444,
      SDL_TEXTUREACCESS_TARGET,
      144,
      144));

  if (!lampFade_)
  {
    throw sdl_error();
  }

  SDL_SetRenderTarget(ren_.get(), lampFade_.get());

  SDL_SetRenderDrawBlendMode(ren_.get(), SDL_BLENDMODE_NONE);
  SDL_SetRenderDrawColor(ren_.get(), 0, 0, 0, 0);
  SDL_RenderClear(ren_.get());
  SDL_RenderCopy(ren_.get(), origFade.get(), nullptr, nullptr);

  SDL_SetRenderDrawBlendMode(ren_.get(), SDL_BLENDMODE_MOD);
  SDL_SetRenderDrawColor(ren_.get(), 255, 204, 58, 255);
  SDL_RenderFillRect(ren_.get(), nullptr);

  dustFade_ = texture_ptr(
    SDL_CreateTexture(
      ren_.get(),
      SDL_PIXELFORMAT_RGBA4444,
      SDL_TEXTUREACCESS_TARGET,
      144,
      144));

  if (!dustFade_)
  {
    throw sdl_error();
  }

  SDL_SetRenderTarget(ren_.get(), dustFade_.get());

  SDL_SetRenderDrawBlendMode(ren_.get(), SDL_BLENDMODE_NONE);
  SDL_SetRenderDrawColor(ren_.get(), 0, 0, 0, 0);
  SDL_RenderClear(ren_.get());
  SDL_RenderCopy(ren_.get(), origFade.get(), nullptr, nullptr);

  SDL_SetRenderDrawBlendMode(ren_.get(), SDL_BLENDMODE_MOD);
  SDL_SetRenderDrawColor(ren_.get(), 255, 150, 255, 255);
  SDL_RenderFillRect(ren_.get(), nullptr);
}

void Renderer::render(
  const Game& game,
  bool drawDark)
{
  texture_ptr canvas(
    SDL_CreateTexture(
      ren_.get(),
      SDL_PIXELFORMAT_RGBA8888,
      SDL_TEXTUREACCESS_TARGET,
      TILE_WIDTH * game.map.getWidth(),
      TILE_HEIGHT * game.map.getHeight()));

  if (!canvas)
  {
    throw sdl_error();
  }

  SDL_SetRenderTarget(ren_.get(), canvas.get());
  SDL_SetRenderDrawBlendMode(ren_.get(), SDL_BLENDMODE_NONE);
  SDL_SetRenderDrawColor(ren_.get(), rand() % 255, rand() % 255, rand() % 255, 255);
  SDL_RenderClear(ren_.get());

  for (int y = game.map.getTop(); y < game.map.getBottom(); y++)
  {
    for (int x = game.map.getLeft(); x < game.map.getRight(); x++)
    {
      bool draw = true;

      if ((game.player_x == x && game.player_y == y) && game.renderPlayer)
      {
        SDL_SetRenderDrawColor(ren_.get(), 255, 255, 0, 255);
      } else if (!game.map.at(x,y).lit)
      {
        if (drawDark)
        {
          SDL_SetRenderDrawColor(ren_.get(), 40, 40, 40, 255);
        } else {
          draw = false;
        }
      } else {
        int alpha = 255;

        switch (game.map.at(x,y).tile)
        {
          case Tile::Floor:
          {
            SDL_SetRenderDrawColor(ren_.get(), 210, 210, 210, alpha);
            break;
          }

          case Tile::Wall:
          {
            SDL_SetRenderDrawColor(ren_.get(), 100, 100, 100, alpha);
            break;
          }

          case Tile::Dust:
          {
            SDL_SetRenderDrawColor(ren_.get(), 128, 40, 255, alpha);
            break;
          }

          case Tile::Lamp:
          {
            SDL_SetRenderDrawColor(ren_.get(), 0, 255, 255, alpha);
            break;
          }
        }
      }

      if (draw)
      {
        SDL_Rect rect {
          game.map.getTrueX(x) * TILE_WIDTH,
          game.map.getTrueY(y) * TILE_HEIGHT,
          TILE_WIDTH,
          TILE_HEIGHT};

        SDL_RenderFillRect(ren_.get(), &rect);
      }
    }
  }

  texture_ptr mask(
    SDL_CreateTexture(
      ren_.get(),
      SDL_PIXELFORMAT_RGBA8888,
      SDL_TEXTUREACCESS_TARGET,
      TILE_WIDTH * game.map.getWidth(),
      TILE_HEIGHT * game.map.getHeight()));

  if (!mask)
  {
    throw sdl_error();
  }

  SDL_SetRenderTarget(ren_.get(), mask.get());
  SDL_SetRenderDrawColor(ren_.get(), 0, 0, 0, 0);
  SDL_RenderClear(ren_.get());

  for (int y = game.map.getTop(); y < game.map.getBottom(); y++)
  {
    for (int x = game.map.getLeft(); x < game.map.getRight(); x++)
    {
      if (game.map.at(x,y).lightType != Source::None)
      {
        texture_ptr sourceMask(
          SDL_CreateTexture(
            ren_.get(),
            SDL_PIXELFORMAT_RGBA8888,
            SDL_TEXTUREACCESS_TARGET,
            TILE_WIDTH * game.map.getWidth(),
            TILE_HEIGHT * game.map.getHeight()));

        if (!sourceMask)
        {
          throw sdl_error();
        }

        SDL_SetRenderTarget(ren_.get(), sourceMask.get());
        SDL_SetRenderDrawBlendMode(ren_.get(), SDL_BLENDMODE_NONE);
        SDL_SetRenderDrawColor(ren_.get(), 0, 0, 0, 0);
        SDL_RenderClear(ren_.get());

        int fadeX = game.map.getTrueX(x) - game.map.at(x,y).lightRadius;
        int fadeY = game.map.getTrueY(y) - game.map.at(x,y).lightRadius;
        int fadeRight = game.map.getTrueX(x) + game.map.at(x,y).lightRadius;
        int fadeBottom = game.map.getTrueY(y) + game.map.at(x,y).lightRadius;

        SDL_Rect fadeRect {
          fadeX * TILE_WIDTH,
          fadeY * TILE_HEIGHT,
          (game.map.at(x,y).lightRadius * 2 + 1) * TILE_WIDTH,
          (game.map.at(x,y).lightRadius * 2 + 1) * TILE_HEIGHT};

        if (game.map.at(x,y).lightType == Source::Lamp)
        {
          SDL_SetTextureBlendMode(lampFade_.get(), SDL_BLENDMODE_NONE);
          SDL_RenderCopy(ren_.get(), lampFade_.get(), nullptr, &fadeRect);
        } else if (game.map.at(x,y).lightType == Source::Player) {
          SDL_SetTextureBlendMode(playerFade_.get(), SDL_BLENDMODE_NONE);
          SDL_RenderCopy(ren_.get(), playerFade_.get(), nullptr, &fadeRect);
        } else if (game.map.at(x,y).lightType == Source::Dust) {
          SDL_SetTextureBlendMode(dustFade_.get(), SDL_BLENDMODE_NONE);
          SDL_RenderCopy(ren_.get(), dustFade_.get(), nullptr, &fadeRect);
        }

        SDL_SetRenderDrawColor(ren_.get(), 0, 0, 0, 0);

        for (int sy = fadeY; sy < fadeBottom; sy++)
        {
          for (int sx = fadeX; sx < fadeRight; sx++)
          {
            if (!game.map.at(x,y).litTiles.count({sx, sy}))
            {
              SDL_Rect rect {
                game.map.getTrueX(sx) * TILE_WIDTH,
                game.map.getTrueY(sy) * TILE_HEIGHT,
                TILE_WIDTH,
                TILE_HEIGHT};

              SDL_RenderFillRect(ren_.get(), &rect);
            }
          }
        }

        SDL_SetRenderTarget(ren_.get(), mask.get());
        SDL_SetTextureBlendMode(sourceMask.get(), SDL_BLENDMODE_ADD);
        SDL_RenderCopy(ren_.get(), sourceMask.get(), nullptr, nullptr);
      }
    }
  }

  SDL_SetRenderTarget(ren_.get(), canvas.get());
  SDL_SetTextureBlendMode(mask.get(), SDL_BLENDMODE_MOD);
  SDL_RenderCopy(ren_.get(), mask.get(), nullptr, nullptr);

  SDL_SetRenderTarget(ren_.get(), nullptr);

  SDL_Rect zoomRect;

  std::tie(zoomRect.x, zoomRect.y, zoomRect.w, zoomRect.h) =
    calculateZoomRect(game);

  SDL_RenderCopy(ren_.get(), canvas.get(), &zoomRect, nullptr);
  SDL_RenderPresent(ren_.get());
}

std::tuple<int, int, int, int> Renderer::calculateZoomRect(const Game& game)
{
  int x = game.map.getTrueX(game.curBoundX) * TILE_WIDTH;
  int y = game.map.getTrueY(game.curBoundY) * TILE_HEIGHT;
  int w = game.curZoom * TILE_WIDTH * ZOOM_X_FACTOR;
  int h = game.curZoom * TILE_HEIGHT * ZOOM_Y_FACTOR;

  if (game.zooming)
  {
    double interp =
      static_cast<double>(game.zoomProgress) /
        static_cast<double>(game.zoomLength);

    x = (x - game.lastZoomLeft) * interp + game.lastZoomLeft;
    y = (y - game.lastZoomTop) * interp + game.lastZoomTop;
    w = (w - game.lastZoomWidth) * interp + game.lastZoomWidth;
    h = (h - game.lastZoomHeight) * interp + game.lastZoomHeight;
  }

  return {x, y, w, h};
}