import {RenderTexture} from '@pixi/core';
import {Container} from '@pixi/display';
import {Graphics} from '@pixi/graphics';
import {PixiComponent, useApp} from '@pixi/react';
import {Sprite} from '@pixi/sprite';
import '@pixi/spritesheet'; // NECESSARY!
import {CompositeTilemap} from '@pixi/tilemap';

import {useAsset} from '@/react/context/assets.js';
import {CHUNK_SIZE, RESOLUTION} from '@/util/constants.js';

import {deferredLighting} from './lights.js';

const TileLayerInternal = PixiComponent('TileLayer', {
  create: ({group, tileLayer}) => {
    const container = new Container();
    const {area, tileSize} = tileLayer;
    const g = new Graphics();
    g.beginFill(group === deferredLighting.diffuseGroup ? 0x000000 : 0x7777ff);
    // outer frame
    g.drawRect(
      -RESOLUTION.x / 2,
      -RESOLUTION.y / 2,
      area.x * tileSize.x + RESOLUTION.x,
      RESOLUTION.y / 2,
    );
    g.drawRect(
      -RESOLUTION.x / 2,
      -RESOLUTION.y / 2,
      RESOLUTION.x / 2,
      area.y * tileSize.y + RESOLUTION.y,
    );
    g.drawRect(
      area.x * tileSize.x,
      0,
      RESOLUTION.x / 2,
      area.y * tileSize.y,
    );
    g.drawRect(
      0,
      area.y * tileSize.y,
      area.x * tileSize.x,
      RESOLUTION.y / 2,
    );
    g.parentGroup = group;
    container.addChild(g);
    const cy = Math.ceil(area.y / CHUNK_SIZE);
    const cx = Math.ceil(area.x / CHUNK_SIZE);
    for (let iy = 0; iy < cy; ++iy) {
      for (let ix = 0; ix < cx; ++ix) {
        const tilemap = new CompositeTilemap();
        const renderTexture = RenderTexture.create({
          width: tileSize.x * CHUNK_SIZE,
          height: tileSize.y * CHUNK_SIZE,
        });
        const sprite = new Sprite(renderTexture);
        sprite.x = tileSize.x * CHUNK_SIZE * ix;
        sprite.y = tileSize.y * CHUNK_SIZE * iy;
        sprite.parentGroup = group;
        sprite.tilemap = tilemap;
        container.addChild(sprite);
      }
    }
    return container;
  },
  applyProps: (container, {tileLayer: oldTileLayer}, props) => {
    const {asset, group, renderer, tileLayer} = props;
    const extless = tileLayer.source.slice('/assets/'.length, -'.json'.length);
    const {textures} = asset;
    if (tileLayer === oldTileLayer) {
      return;
    }
    for (const i in tileLayer.$$chunks) {
      if (!oldTileLayer || oldTileLayer.$$chunks[i] !== tileLayer.$$chunks[i]) {
        const {texture, tilemap} = container.children[parseInt(i) + 1];
        tilemap.clear();
        const ax = Math.ceil(tileLayer.area.x / CHUNK_SIZE);
        const cy = Math.floor(i / ax);
        const cx = i % ax;
        for (let y = 0; y < CHUNK_SIZE; ++y) {
          for (let x = 0; x < CHUNK_SIZE; ++x) {
            const ty = (cy * CHUNK_SIZE) + y;
            const tx = (cx * CHUNK_SIZE) + x;
            if (
              tx < 0
              || ty < 0
              || tx >= tileLayer.area.x
              || ty >= tileLayer.area.y
            ) {
              continue;
            }
            const parts = [
              extless,
              ...(deferredLighting.normalGroup === group ? ['normals'] : []),
              tileLayer.data[ty * tileLayer.area.x + tx],
            ];
            tilemap.tile(
              textures[parts.join('/')],
              tileLayer.tileSize.x * x,
              tileLayer.tileSize.y * y,
              {
                tileWidth: 16,
                tileHeight: 16,
              },
            );
          }
        }
        renderer.render(tilemap, {renderTexture: texture});
      }
    }
  },
});

export default function TileLayer(props) {
  const {renderer} = useApp();
  const {tileLayer} = props;
  const asset = useAsset(tileLayer.source);
  const normalsAsset = useAsset([tileLayer.source.slice(0, -'.json'.length), 'normals.json'].join('.'));
  if (!asset || !normalsAsset) {
    return false;
  }
  return (
    <>
      <TileLayerInternal
        {...props}
        asset={asset}
        group={deferredLighting.diffuseGroup}
        renderer={renderer}
      />
      <TileLayerInternal
        {...props}
        asset={normalsAsset}
        group={deferredLighting.normalGroup}
        renderer={renderer}
      />
    </>
  );
}
