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

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

  //SDL_SetWindowFullscreen(win_.get(), SDL_WINDOW_FULLSCREEN_DESKTOP);

  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;
  loadTextureFromFile("../res/lighting.png", origFade);

  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);

  SDL_SetRenderDrawColor(ren_.get(), 100, 100, 100, 255);
  SDL_RenderFillRect(ren_.get(), nullptr);

  loadTextureFromFile("../res/player.png", playerSheet_);
  loadTextureFromFile("../res/runninbloods.png", tileset_);
  loadTextureFromFile("../res/lamp.png", lamp_);

  loadTextureFromFile("../res/title0.png", titles_[0]);
  SDL_QueryTexture(titles_[0].get(), nullptr, nullptr, &titleWidths_[0], &titleHeights_[0]);
}

void Renderer::renderGame(
  const Game& game,
  bool drawDark)
{
  int windowTileWidth = game.curZoom * ZOOM_X_FACTOR + 2;
  int windowTileHeight = game.curZoom * ZOOM_Y_FACTOR + 2;

  texture_ptr canvas(
    SDL_CreateTexture(
      ren_.get(),
      SDL_PIXELFORMAT_RGBA8888,
      SDL_TEXTUREACCESS_TARGET,
      windowTileWidth * TILE_WIDTH,
      windowTileHeight * TILE_HEIGHT));

  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());

  int leftmost = game.player_x - game.curZoom * ZOOM_X_FACTOR / 2 - 1;
  int topmost = game.player_y - game.curZoom * ZOOM_Y_FACTOR / 2 - 1;
  for (int y = topmost; y < topmost + windowTileHeight; y++)
  {
    for (int x = leftmost; x < leftmost + windowTileWidth; x++)
    {
      bool draw = true;
      bool drawColour = false;
      SDL_Rect rect {
        (x - leftmost) * TILE_WIDTH,
        (y - topmost) * TILE_HEIGHT,
        TILE_WIDTH,
        TILE_HEIGHT};

      if (!game.map.at(x,y).lit)
      {
        if (drawDark)
        {
          SDL_SetRenderDrawColor(ren_.get(), 40, 40, 40, 255);
          drawColour = true;
        } else {
          draw = false;
        }
      }

      if (draw)
      {
        if (game.map.at(x,y).tile != Tile::Wall) {
          SDL_Rect tileRect {17 * 16, 15 * 16, 16, 16};
          SDL_RenderCopy(ren_.get(), tileset_.get(), &tileRect, &rect);

          if (game.map.at(x,y).renderId != -1) {
            tileRect.x = game.map.at(x,y).renderId % 24 * 16;
            tileRect.y = game.map.at(x,y).renderId / 24 * 16;
            SDL_RenderCopy(ren_.get(), tileset_.get(), &tileRect, &rect);
          }
        } else {
          SDL_Rect tileRect {
            game.map.at(x,y).renderId % 24 * 16,
            game.map.at(x,y).renderId / 24 * 16,
            16,
            16};

          SDL_RenderCopy(ren_.get(), tileset_.get(), &tileRect, &rect);
        }

        if (game.map.at(x,y).tile == Tile::Lamp) {
          SDL_RenderCopy(ren_.get(), lamp_.get(), nullptr, &rect);
        }

        if (drawColour) {
          SDL_RenderFillRect(ren_.get(), &rect);
        }
      }
    }
  }

  if (game.renderPlayer) {
    SDL_Rect rect {
      (game.player_x - leftmost) * TILE_WIDTH,
      (game.player_y - topmost) * TILE_HEIGHT,
      TILE_WIDTH,
      TILE_HEIGHT};

    if (game.moving) {
      rect.x = game.moveProgress.getProgress((game.player_oldx - leftmost) * TILE_WIDTH, rect.x);
      rect.y = game.moveProgress.getProgress((game.player_oldy - topmost) * TILE_HEIGHT, rect.y);
    }

    SDL_RenderCopy(ren_.get(), playerSheet_.get(), &game.playerAnim.getRenderRect(), &rect);
  }

  texture_ptr mask(
    SDL_CreateTexture(
      ren_.get(),
      SDL_PIXELFORMAT_RGBA8888,
      SDL_TEXTUREACCESS_TARGET,
      windowTileWidth * TILE_WIDTH,
      windowTileHeight * TILE_HEIGHT));

  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 = topmost; y < topmost + windowTileHeight; y++)
  {
    for (int x = leftmost; x < leftmost + windowTileWidth; x++)
    {
      if (game.map.at(x,y).lightType != Source::None)
      {
        texture_ptr sourceMask(
          SDL_CreateTexture(
            ren_.get(),
            SDL_PIXELFORMAT_RGBA8888,
            SDL_TEXTUREACCESS_TARGET,
            windowTileWidth * TILE_WIDTH,
            windowTileHeight * TILE_HEIGHT));

        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 posToUseX = x - leftmost;
        int posToUseY = y - topmost;
        if (game.map.at(x,y).lightType == Source::Player && game.moving) {
          posToUseX = game.moveProgress.getProgress((game.player_oldx) - leftmost, posToUseX);
          posToUseY = game.moveProgress.getProgress((game.player_oldy) - topmost, posToUseY);
        }

        int fadeX = posToUseX - game.map.at(x,y).lightRadius;
        int fadeY = posToUseY - game.map.at(x,y).lightRadius;
        int fadeRight = posToUseX + game.map.at(x,y).lightRadius;
        int fadeBottom = posToUseY + 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 {
                (sx - leftmost) * TILE_WIDTH,
                (sy - topmost) * 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);

  // TODO: this is just moving interp. we also need zoom
  SDL_Rect zoomRect {
    TILE_WIDTH, TILE_HEIGHT, (windowTileWidth - 2) * TILE_WIDTH, (windowTileHeight - 2) * TILE_HEIGHT
  };

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

    if (game.player_x > game.player_oldx) {
      zoomRect.x = game.moveProgress.getProgress(0, TILE_WIDTH);
    } else if (game.player_x < game.player_oldx) {
      zoomRect.x = game.moveProgress.getProgress(2*TILE_WIDTH, TILE_WIDTH);
    }

    if (game.player_y > game.player_oldy) {
      zoomRect.y = game.moveProgress.getProgress(0, TILE_HEIGHT);
    } else if (game.player_y < game.player_oldy) {
      zoomRect.y = game.moveProgress.getProgress(TILE_HEIGHT*2, TILE_HEIGHT);
    }
  }

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

void Renderer::renderTitle(int num, double fade) {
  texture_ptr canvas(
    SDL_CreateTexture(
      ren_.get(),
      SDL_PIXELFORMAT_RGBA8888,
      SDL_TEXTUREACCESS_TARGET,
      GAME_WIDTH,
      GAME_HEIGHT));

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

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

  if (fade > 0) {
    SDL_Rect rect {
      (GAME_WIDTH - titleWidths_[num]) / 2,
      (GAME_HEIGHT - titleHeights_[num]) / 2,
      titleWidths_[num],
      titleHeights_[num]
    };

    SDL_RenderCopy(ren_.get(), titles_[num].get(), nullptr, &rect);

    if (fade < 1) {
      SDL_SetRenderDrawBlendMode(ren_.get(), SDL_BLENDMODE_BLEND);
      SDL_SetRenderDrawColor(ren_.get(), 0, 0, 0, (1.0 - fade) * 255);
      SDL_RenderFillRect(ren_.get(), nullptr);
    }
  }

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

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

  /*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};
}

void Renderer::loadTextureFromFile(std::string_view path, texture_ptr& texture) {
  surface_ptr pfs(IMG_Load(path.data()));
  if (!pfs)
  {
    throw img_error();
  }

  texture = texture_ptr(SDL_CreateTextureFromSurface(ren_.get(), pfs.get()));
  SDL_SetTextureBlendMode(texture.get(), SDL_BLENDMODE_BLEND);
}