import {NextPage} from 'next';
import NextImage from 'next/image';
import styles from '../styles/pages/index.module.css';
import {GenerateMapForm} from '../components/GenerateMapForm';
import getFormValues from '@theoryofnekomata/formxtra';
import {
  ChangeEventHandler,
  FormEventHandler,
  MouseEventHandler,
  UIEventHandler,
  useEffect,
  useRef,
  useState,
} from 'react';
import {MapType, WaterType, WorldData} from '../utils/types';
import WORLD_BACKGROUND from '../assets/gfx/world_background.png';
import WORLD_FOREGROUND from '../assets/gfx/world_foreground.png';
import {DropdownSelect} from '../components/DropdownSelect';
import {ActionButton} from '../components/ActionButton';

const IMAGE_URLS_INDEX = [
  MapType.BIOME,
  MapType.ELEVATION,
  MapType.WATER_MASK,
  MapType.BATHYMETRY,
  MapType.REAL_COLOR,
]

const IMAGE_STACKING = [
  MapType.WATER_MASK,
  MapType.BATHYMETRY,
  MapType.ELEVATION,
  MapType.REAL_COLOR,
  MapType.BIOME,
]

const loadImage = async (src: string, processTransparency = false) => {
  return new Promise<HTMLImageElement>((resolve, reject) => {
    const originalImage = new Image()
    originalImage.addEventListener('load', (e) => {
      const theOriginalImage = e.target as HTMLImageElement

      if (!processTransparency) {
        resolve(theOriginalImage)
        return
      }

      const tempCanvas = window.document.createElement('canvas')
      tempCanvas.width = theOriginalImage.width
      tempCanvas.height = theOriginalImage.height
      const context = tempCanvas.getContext('2d')

      if (!context) {
        reject()
        return
      }

      context.drawImage(e.target as HTMLImageElement, 0, 0)
      const oldImageData = context.getImageData(0, 0, theOriginalImage.width, theOriginalImage.height)
      const newImageData = context.createImageData(oldImageData)
      for (let y = 0; y < oldImageData.height; y += 1) {
        for (let x = 0; x < oldImageData.width; x += 1) {
          const i = ((y * oldImageData.width) + x) * 4;
          if (oldImageData.data[i] === 255 && oldImageData.data[i + 1] === 0 && oldImageData.data[i + 2] === 255) {
            newImageData.data[i] = 0
            newImageData.data[i + 1] = 0
            newImageData.data[i + 2] = 0
            newImageData.data[i + 3] = 0
            continue;
          }

          newImageData.data[i] = oldImageData.data[i]
          newImageData.data[i + 1] = oldImageData.data[i + 1]
          newImageData.data[i + 2] = oldImageData.data[i + 2]
          newImageData.data[i + 3] = oldImageData.data[i + 3]
        }
      }
      context.clearRect(0, 0, theOriginalImage.width, theOriginalImage.height)
      context.putImageData(newImageData, 0, 0)

      const modifiedImage = new Image()
      modifiedImage.addEventListener('load', (e2) => {
        resolve(e2.target as HTMLImageElement)
      })

      modifiedImage.addEventListener('error', () => {
        reject()
      })

      modifiedImage.src = tempCanvas.toDataURL()
    })

    originalImage.addEventListener('error', () => {
      reject()
    })

    originalImage.src = src
  })
}

type Data = {
  worldData: WorldData,
  baseDataImageUrls: string[],
}

const drawWater = (context: CanvasRenderingContext2D, backgroundImage: HTMLImageElement, spriteWaterLayer: number[][], x: number, y: number) => {
  const dx = x * 16;
  const dy = y * 16;

  // TODO animate water
  switch (spriteWaterLayer[y][x]) {
  case WaterType.LIGHT:
    context.drawImage(backgroundImage, 20 * 16, 0, 16, 16, dx, dy, 16, 16);
    return;
  case WaterType.LAVA:
    context.drawImage(backgroundImage, 24 * 16, 0, 16, 16, dx, dy, 16, 16);
    return;
  default:
    break;
  }
  context.drawImage(backgroundImage, 0, 0, 16, 16, dx, dy, 16, 16);
}

const drawLand = (context: CanvasRenderingContext2D, backgroundImage: HTMLImageElement, spriteBackgroundLayer: number[][], x: number, y: number) => {
  const tileIndex = spriteBackgroundLayer[y][x] % 60
  const tileVariant = Math.floor(spriteBackgroundLayer[y][x] / 60)
  const dx = x * 16;
  const dy = y * 16;
  let tx = 4 * tileVariant;
  let ty = 1;

  switch (tileIndex) {
  case 0:
    return;
  case 1:
    tx += 1;
    break;
  default:
    if (tileIndex < 30) {
      tx += Math.floor((tileIndex - 2) / 14);
      ty = (tileIndex - 2) % 14 + 2;
    } else if (tileIndex < 60) {
      tx += Math.floor(tileIndex / 15);
      ty = tileIndex % 15 + 1;
    }
    break;
  }

  context.drawImage(backgroundImage, tx * 16, ty * 16, 16, 16, dx, dy, 16, 16);
}

const drawObjects = (context: CanvasRenderingContext2D, foregroundImage: HTMLImageElement, spriteBackgroundLayer: number[][], x: number, y: number) => {
  const dx = x * 16;
  const dy = y * 16;
  let tx: number;
  let ty: number;
  if (spriteBackgroundLayer[y][x] < 900) {
    tx = (spriteBackgroundLayer[y][x] - 700) % 12
    ty = Math.floor((spriteBackgroundLayer[y][x] - 700) / 12)
  } else {
    tx = 13
    ty = spriteBackgroundLayer[y][x] - 900
    // fixme animated sprites in second column
  }
  context.drawImage(foregroundImage, tx * 16, ty * 16, 16, 16, dx, dy, 16, 16);
}

const drawWorld = async (canvas: HTMLCanvasElement, data: Data) => {
  const context = canvas.getContext('2d')
  if (!context) {
    return
  }

  context.clearRect(0, 0, canvas.width, canvas.height)
  const backgroundImage = await loadImage(WORLD_BACKGROUND.src, true)
  const foregroundImage = await loadImage(WORLD_FOREGROUND.src, true)

  for (let y = 0; y < data.worldData.height; y += 1) {
    for (let x = 0; x < data.worldData.width; x += 1) {
      drawWater(context, backgroundImage, data.worldData.spriteWaterLayer, x, y);
      drawLand(context, backgroundImage, data.worldData.spriteBackgroundLayer, x, y);
      drawObjects(context, foregroundImage, data.worldData.spriteForegroundLayer, x, y);
    }
  }
}

const createSuperMarioWarWorldFile = (worldData: WorldData) => {
  return `#Version
${worldData.version}

#Music Category
${worldData.musicCategory}

#Width
${worldData.width}

#Height
${worldData.height}

#Sprite Water Layer
${worldData.spriteWaterLayer.map(s => s.join(',')).join('\n')}

#Sprite Background Layer
${worldData.spriteBackgroundLayer.map(s => s.join(',')).join('\n')}

#Sprite Foreground Layer
${worldData.spriteForegroundLayer.map(s => s.join(',')).join('\n')}

#Connections
${new Array(worldData.height).fill(new Array(worldData.width).fill('0').join(',')).join('\n')}

#Tile Types (Stages, Doors, Start Tiles)
${new Array(worldData.height).fill(new Array(worldData.width).fill('0').join(',')).join('\n')}

#Vehicle Boundaries
${new Array(worldData.height).fill(new Array(worldData.width).fill('0').join(',')).join('\n')}

#Stages
#Stage Type 0,Map,Mode,Goal,Points,Bonus List(Max 10),Name,End World, then mode settings (see sample tour file for details)
#Stage Type 1,Bonus House Name,Sequential/Random Order,Display Text,Powerup List(Max 5)
0

#Warps
#location 1 x, y, location 2 x, y
0

#Vehicles
#Sprite,Stage Type, Start Column, Start Row, Min Moves, Max Moves, Sprite Paces, Sprite Direction, Boundary
0

#Initial Items
0
`
}

const IndexPage: NextPage = () => {
  const [data, setData] = useState<Data>()
  const [previewMapOpacity, setPreviewMapOpacity] = useState<Record<MapType, number>>(() => {
    return IMAGE_URLS_INDEX.reduce(
      (theValue, mapType) => {
        return {
          ...theValue,
          [mapType]: 1,
        }
      },
      {} as Record<MapType, number>
    );
  })
  const canvasRef = useRef<HTMLCanvasElement>(null)
  const baseMapScrollRef = useRef<HTMLDivElement>(null)
  const worldScrollRef = useRef<HTMLDivElement>(null)
  const scrollRef = useRef(false)
  const handleSubmit: FormEventHandler<HTMLFormElement> = async (e) => {
    e.preventDefault()
    const form = e.target as HTMLFormElement
    const values = getFormValues(form)
    const url = new URL('/api/generate', 'http://localhost:3000');
    const search = new URLSearchParams(values)
    url.search = search.toString()
    const response = await fetch(url.toString(), {
      method: 'GET',
      headers: {
        'Accept': 'application/json',
      },
    })
    const responseData = await response.json()
    if (response.ok) {
      setData(responseData)
    }
  }

  const saveScreenshot: MouseEventHandler = async (e) => {
    e.preventDefault()
    if (!canvasRef.current) {
      return;
    }
    canvasRef.current.toBlob((blob) => {
      const url = URL.createObjectURL(blob as Blob)
      const a = window.document.createElement('a')
      a.href = url
      a.download = 'world.png';
      a.click()
    }, 'image/png');
  }

  const saveWorld: MouseEventHandler = async (e) => {
    e.preventDefault()
    if (!data) {
      return;
    }

    const blob = new Blob([
      createSuperMarioWarWorldFile(data.worldData)
    ], {
      type: 'text/plain',
    })
    const url = URL.createObjectURL(blob)
    const a = window.document.createElement('a')
    a.href = url
    a.download = 'world.txt';
    a.click()
  }

  const changePreviewMapOpacity: ChangeEventHandler<HTMLInputElement> = (e) => {
    const { currentTarget } = e
    const { value, name } = currentTarget
    setPreviewMapOpacity(oldMapOpacity => ({
      ...oldMapOpacity,
      [name as MapType]: value,
    }));
  }

  const handleBaseMapScroll: UIEventHandler<HTMLDivElement> = (e) => {
    if (!worldScrollRef.current) {
      return;
    }

    if (scrollRef.current) {
      scrollRef.current = false;
      return;
    }

    scrollRef.current = true;
    const target = e.currentTarget
    if (target.scrollHeight > target.offsetHeight) {
      worldScrollRef.current.scrollTop = Math.floor(target.scrollTop / (target.scrollHeight - target.offsetHeight) * (worldScrollRef.current.scrollHeight - worldScrollRef.current.offsetHeight));
    }
    if (target.scrollWidth > target.offsetWidth) {
      worldScrollRef.current.scrollLeft = Math.floor(target.scrollLeft / (target.scrollWidth - target.offsetWidth) * (worldScrollRef.current.scrollWidth - worldScrollRef.current.offsetWidth));
    }
  }

  const handleWorldScroll: UIEventHandler<HTMLDivElement> = (e) => {
    if (!baseMapScrollRef.current) {
      return;
    }

    if (scrollRef.current) {
      scrollRef.current = false;
      return;
    }

    scrollRef.current = true;
    const target = e.currentTarget
    if (target.scrollHeight > target.offsetHeight) {
      baseMapScrollRef.current.scrollTop = Math.floor(target.scrollTop / (target.scrollHeight - target.offsetHeight) * (baseMapScrollRef.current.scrollHeight - baseMapScrollRef.current.offsetHeight));
    }
    if (target.scrollWidth > target.offsetWidth) {
      baseMapScrollRef.current.scrollLeft = Math.floor(target.scrollLeft / (target.scrollWidth - target.offsetWidth) * (baseMapScrollRef.current.scrollWidth - baseMapScrollRef.current.offsetWidth));
    }
  }

  useEffect(() => {
    if (!data) {
      return
    }

    if (!canvasRef.current) {
      return
    }

    void drawWorld(canvasRef.current, data);
  }, [data, canvasRef])

  return (
    <div
      className={styles.base}
    >
      <div
        className={styles.ui}
      >
        <form
          className={styles.form}
          onSubmit={handleSubmit}
        >
          <GenerateMapForm />
        </form>
      </div>
      <div
        className={styles.map}
      >
        {
          data
          && (
            <>
              <div
                className={styles.mapScroll}
                onScroll={handleBaseMapScroll}
                ref={baseMapScrollRef}
              >
                {
                  IMAGE_STACKING.map((v: MapType) => (
                    <img
                      key={v}
                      src={data.baseDataImageUrls[IMAGE_URLS_INDEX.indexOf(v)]}
                      alt={v}
                      style={{
                        opacity: previewMapOpacity[v],
                      }}
                      className={styles.baseMapCanvas}
                    />
                  ))
                }
              </div>
              <div
                className={styles.mapForm}
              >
                {
                  IMAGE_STACKING.map((v: MapType) => (
                    <div
                      key={v}
                    >
                      <label>
                        <span
                          className={styles.fieldLabel}
                        >
                          {v}
                        </span>
                        <input
                          type="range"
                          min={0}
                          max={1}
                          step={0.01}
                          defaultValue={1}
                          name={v}
                          onChange={changePreviewMapOpacity}
                        />
                      </label>
                    </div>
                  ))
                }
              </div>
            </>
          )
        }
      </div>
      <div
        className={styles.map}
      >
        {
          data
          && (
            <>
              <div
                className={styles.mapScroll}
                onScroll={handleWorldScroll}
                ref={worldScrollRef}
              >
                <canvas
                  width={data.worldData.width * 16}
                  height={data.worldData.height * 16}
                  ref={canvasRef}
                  className={styles.mapCanvas}
                />
              </div>
              <div
                className={styles.mapForm}
              >
                <ActionButton
                  onClick={saveWorld}
                >
                  Save World
                </ActionButton>
                {' '}
                <ActionButton
                  onClick={saveScreenshot}
                >
                  Save Screenshot
                </ActionButton>
              </div>
            </>
          )
        }
      </div>
    </div>
  )
}

export default IndexPage;
