import { PixiComponent, applyDefaultProps } from '@pixi/react';
import * as PIXI from 'pixi.js';
import { AnimatedSprite, WorldMap, TileLayer } from '../../convex/aiTown/worldMap';
import * as campfire from '../../data/animations/campfire.json';
import * as gentlesparkle from '../../data/animations/gentlesparkle.json';
import * as gentlewaterfall from '../../data/animations/gentlewaterfall.json';
import * as gentlesplash from '../../data/animations/gentlesplash.json';
import * as windmill from '../../data/animations/windmill.json';
import React from 'react';

export type MapProps = {
  map: WorldMap;
  [k: string]: any;
};

const animations = {
  'campfire.json': { spritesheet: campfire, url: '/assets/spritesheets/campfire.png' },
  'gentlesparkle.json': {
    spritesheet: gentlesparkle,
    url: '/assets/spritesheets/gentlesparkle32.png',
  },
  'gentlewaterfall.json': {
    spritesheet: gentlewaterfall,
    url: '/assets/spritesheets/gentlewaterfall32.png',
  },
  'windmill.json': { spritesheet: windmill, url: '/assets/spritesheets/windmill.png' },
  'gentlesplash.json': { spritesheet: gentlesplash,
    url: '/assets/spritesheets/gentlewaterfall32.png',},
};

const createTiles = (map: WorldMap) => {
  const numxtiles = Math.floor(map.tileSetDimX / map.tileDim);
  const numytiles = Math.floor(map.tileSetDimY / map.tileDim);
  const bt = PIXI.BaseTexture.from(map.tileSetUrl, {
    scaleMode: PIXI.SCALE_MODES.NEAREST,
  });

  const tiles = [];
  for (let x = 0; x < numxtiles; x++) {
    for (let y = 0; y < numytiles; y++) {
      tiles[x + y * numxtiles] = new PIXI.Texture(
        bt,
        new PIXI.Rectangle(x * map.tileDim, y * map.tileDim, map.tileDim, map.tileDim),
      );
    }
  }
  return tiles;
};

const renderMap = (container: PIXI.Container, map:WorldMap, tiles: PIXI.Texture[]) => {
  const screenxtiles = map.bgTiles[0].length;
  const screenytiles = map.bgTiles[0][0].length;

  const allLayers = [...map.bgTiles, ...map.objectTiles, ...map.decorTiles];

  // blit bg & object layers of map onto canvas
  for (let i = 0; i < screenxtiles * screenytiles; i++) {
    const x = i % screenxtiles;
    const y = Math.floor(i / screenxtiles);
    const xPx = x * map.tileDim;
    const yPx = y * map.tileDim;

    // Add all layers of backgrounds.
    for (const layer of allLayers) {
      const tileIndex = layer[x][y];
      // Some layers may not have tiles at this location.
      if (tileIndex === -1) continue;
      const ctile = new PIXI.Sprite(tiles[tileIndex]);
      ctile.x = xPx;
      ctile.y = yPx;
      container.addChild(ctile);
    }
  }
};

const createAnimatedSprites = (container: PIXI.Container, map: WorldMap) => {
  const spritesBySheet = new Map<string, AnimatedSprite[]>();
  for (const sprite of map.animatedSprites) {
    const sheet = sprite.sheet;
    if (!spritesBySheet.has(sheet)) {
      spritesBySheet.set(sheet, []);
    }
    spritesBySheet.get(sheet)!.push(sprite);
  }

  for (const [sheet, sprites] of spritesBySheet.entries()) {
    const animation = (animations as any)[sheet];
    if (!animation) {
      console.error('Could not find animation', sheet);
      continue;
    }
    const { spritesheet, url } = animation;
    const texture = PIXI.BaseTexture.from(url, {
      scaleMode: PIXI.SCALE_MODES.NEAREST,
    });
    const spriteSheet = new PIXI.Spritesheet(texture, spritesheet);
    spriteSheet.parse().then(() => {
      for (const sprite of sprites) {
        const pixiAnimation = spriteSheet.animations[sprite.animation];
        if (!pixiAnimation) {
          console.error('Failed to load animation', sprite);
          continue;
        }
        const pixiSprite = new PIXI.AnimatedSprite(pixiAnimation);
        pixiSprite.animationSpeed = 0.1;
        pixiSprite.autoUpdate = true;
        pixiSprite.x = sprite.x;
        pixiSprite.y = sprite.y;
        pixiSprite.width = sprite.w;
        pixiSprite.height = sprite.h;
        container.addChild(pixiSprite);
        pixiSprite.play();
      }
    });
  }
};

export const PixiStaticMapComponent = PixiComponent('StaticMap', {
  create: (props: { map: WorldMap; [k: string]: any }) => {
    const container = new PIXI.Container();
    const tiles = createTiles(props.map);
    renderMap(container, props.map, tiles);
    createAnimatedSprites(container, props.map);

    container.x = 0;
    container.y = 0;

    // Set the hit area manually to ensure `pointerdown` events are delivered to this container.
    const screenxtiles = props.map.bgTiles[0].length;
    const screenytiles = props.map.bgTiles[0][0].length;
    container.interactive = true;
    container.hitArea = new PIXI.Rectangle(
      0,
      0,
      screenxtiles * props.map.tileDim,
      screenytiles * props.map.tileDim,
    );

    return container;
  },

  applyProps: (instance, oldProps: any, newProps: any) => {
    if (oldProps.map !== newProps.map) {
      instance.removeChildren();
      const tiles = createTiles(newProps.map);
      renderMap(instance, newProps.map, tiles);
      createAnimatedSprites(instance, newProps.map);
    }

    applyDefaultProps(instance, oldProps, newProps);
  },
});

const PixiStaticMap = React.memo(
  (props: MapProps) => {
    return <PixiStaticMapComponent {...props} />;
  },
  (prevProps, nextProps) => {
    return (
      prevProps.map.bgTiles === nextProps.map.bgTiles &&
      prevProps.map.objectTiles === nextProps.map.objectTiles &&
      prevProps.map.decorTiles === nextProps.map.decorTiles
    );
  }
);

export default PixiStaticMap;
