import BiomeService, {BiomeServiceImpl} from './Biome.service';
import {LandType, WaterType} from '../../utils/types';

export type NeighborData = {
  nw: boolean,
  n: boolean,
  ne: boolean,
  w: boolean,
  i: number,
  e: boolean,
  sw: boolean,
  s: boolean,
  se: boolean,
}

export default interface TileService {
  determineBackgroundTileType(x: number, y: number, width: number, height: number): number
  determineForegroundTileType(tileType: number, x: number, y: number, dx: number): number;
  determineWaterBackgroundTileType(x: number, y: number, dx: number): number;
}

export class TileServiceImpl implements TileService {
  private index(x: number, y: number, width: number) {
    return ((y * width) + x) * 4;
  }

  private readonly landThreshold = 0x80

  private readonly SHALLOW_WATER_THRESHOLD = 0xE0

  private checkNeighboringLands(waterMaskPngData: Buffer, x: number, y: number, width: number, height: number): NeighborData {
    const ny = y === 0 ? 0 : y - 1;
    const wx = (x + width - 1) % width;
    const ex = (x + 1) % width ;
    const sy = y === height - 1 ? height - 1 : y + 1;

    return {
      nw: waterMaskPngData[this.index(wx, ny, width)] < this.landThreshold,
      n: waterMaskPngData[this.index(x, ny, width)] < this.landThreshold,
      ne: waterMaskPngData[this.index(ex, ny, width)] < this.landThreshold,
      w: waterMaskPngData[this.index(wx, y, width)] < this.landThreshold,
      i: waterMaskPngData[this.index(x, y, width)],
      e: waterMaskPngData[this.index(ex, y, width)] < this.landThreshold,
      sw: waterMaskPngData[this.index(wx, sy, width)] < this.landThreshold,
      s: waterMaskPngData[this.index(x, sy, width)] < this.landThreshold,
      se: waterMaskPngData[this.index(ex, sy, width)] < this.landThreshold,
    };
  }

  private determineCoastalBackgroundTileType(landFlags: NeighborData) {
    let cursor = 0;
    if (landFlags.n) {
      cursor += 12;            // 12
      if (landFlags.e) {
        cursor += 12;          // 24
        if (landFlags.s) {
          cursor += 14;        // 38
          if (landFlags.w) {
            cursor -= 10;      // 28
          }
        } else if (landFlags.w) {
          cursor += 16;        // 40
        } else if (landFlags.sw) {
          cursor += 12;        // 36
        }
      } else if (landFlags.w) {
        cursor += 15;          // 27
        if (landFlags.s) {
          cursor += 12;        // 39
        } else if (landFlags.se) {
          cursor += 10;        // 37
        }
      } else if (landFlags.s) {
        cursor += 31;          // 43
      } else if (landFlags.se) {
        cursor += 8;           // 20
        if (landFlags.sw) {
          cursor += 12;        // 32
        }
      } else if (landFlags.sw) {
        cursor += 11;          // 23
      }
    } else if (landFlags.s) {
      cursor += 13;            // 13
      if (landFlags.e) {
        cursor += 12;          // 25
        if (landFlags.w) {
          cursor += 16;        // 41
        } else if (landFlags.nw) {
          cursor += 10;        // 35
        }
      } else if (landFlags.w) {
        cursor += 13;          // 26
        if (landFlags.ne) {
          cursor += 8;         // 34
        }
      } else if (landFlags.ne) {
        cursor += 8;           // 21
        if (landFlags.nw) {
          cursor += 20;        // 33
        }
      } else if (landFlags.nw) {
        cursor += 9;           // 22
      }
    } else if (landFlags.w) {
      cursor += 14;            // 14
      if (landFlags.e) {
        cursor += 28;          // 42
      } else if (landFlags.se) {
        cursor += 4;           // 18
        if (landFlags.ne) {
          cursor += 13;        // 31
        }
      } else if (landFlags.ne) {
        cursor += 5;           // 19
      }
    } else if (landFlags.e) {
      cursor += 15;            // 15
      if (landFlags.nw) {
        cursor += 1            // 16
        if (landFlags.sw) {
          cursor += 14;        // 30
        }
      } else if (landFlags.sw) {
        cursor += 2;           // 17
      }
    } else if (landFlags.ne) {
      cursor += 2;             // 2
      if (landFlags.nw) {
        cursor += 4;           // 6
        if (landFlags.sw) {
          cursor += 42;        // 48
          if (landFlags.se) {
            cursor -= 4;       // 44
          }
        } else if (landFlags.se) {
          cursor += 39;        // 45
        }
      } else if (landFlags.se) {
        cursor += 7;           // 9
        if (landFlags.sw) {
          cursor += 37;        // 46
        }
      } else if (landFlags.sw) {
        cursor += 8;           // 10
      }
    } else if (landFlags.nw) {
      cursor += 3;             // 3
      if (landFlags.sw) {
        cursor += 5;           // 8
        if (landFlags.se) {
          cursor += 39;        // 47
        }
      } else if (landFlags.se) {
        cursor += 8;           // 11
      }
    } else if (landFlags.sw) {
      cursor += 4;             // 4
      if (landFlags.se) {
        cursor += 3;           // 7
      }
    } else if (landFlags.se) {
      cursor += 5;             // 5
    }

    return cursor;
  }

  private determineBaseBackgroundTileType(waterMaskPngData: Buffer, x: number, y: number, width: number, height: number): number {
    const landFlags = this.checkNeighboringLands(waterMaskPngData, x, y, width, height);

    const islandThreshold = 0xE0;
    if (landFlags.i < this.landThreshold) {
      return 1;                // 1
    }

    if (
      landFlags.i < islandThreshold
      && !(
        landFlags.nw
        || landFlags.n
        || landFlags.ne
        || landFlags.w
        || landFlags.e
        || landFlags.sw
        || landFlags.s
        || landFlags.se
      )
    ) {
      return 29;              // 29
    }

    return this.determineCoastalBackgroundTileType(landFlags)
  }

  private readonly biomeService: BiomeService

  private getMountainForegroundTileType(landType: LandType): number {
    switch (landType) {
    case LandType.DESERT:
    case LandType.MESA:
      return 796;
    case LandType.ICE:
      return 799;
    case LandType.FOREST:
    case LandType.PLAINS:
    default:
      return 797;
    }
  }

  private getHillForegroundTileType(landType: LandType): number {
    if (landType === LandType.ICE) {
      return 903
    }
    return 901
  }

  private getWoodedForegroundTileType(landType: LandType): number {
    if (landType === LandType.ICE) {
      return 906
    }
    return 905
  }

  constructor(
    biomePngData: Buffer,
    private readonly elevaionPngData: Buffer,
    private readonly waterMaskPngData: Buffer,
    private readonly bathymetryPngData: Buffer,
  ) {
    this.biomeService = new BiomeServiceImpl(biomePngData)
  }

  determineBackgroundTileType(x: number, y: number, width: number, height: number): number {
    const landType = this.biomeService.checkBiomeLandType(x, y, width);
    const tileType = this.determineBaseBackgroundTileType(this.waterMaskPngData, x, y, width, height);
    return ((landType * 60) + tileType);
  }

  private determineLandForegroundTileType(x: number, y: number, dx: number): number {
    const landType = this.biomeService.checkBiomeLandType(x, y, dx);

    const i = this.index(x, y, dx);
    const isMountain = this.elevaionPngData[i] >= 0x40 || this.biomeService.isMountainBiome(
      x,
      y,
      dx
    );
    if (isMountain) {
      return this.getMountainForegroundTileType(landType);
    }

    if (this.biomeService.isHillBiome(x, y, dx)) {
      return this.getHillForegroundTileType(landType)
    }

    if (this.biomeService.isWoodedBiome(x, y, dx)) {
      return this.getWoodedForegroundTileType(landType);
    }

    return 0;
  }

  private determineWaterForegroundTileType(x: number, y: number, dx: number): number {
    return 0;
  }

  determineWaterBackgroundTileType(x: number, y: number, dx: number): number {
    const i = this.index(x, y, dx);
    const isShallowWater = this.bathymetryPngData[i] >= this.SHALLOW_WATER_THRESHOLD;
    return isShallowWater ? WaterType.LIGHT : WaterType.DEFAULT;
  }

  determineForegroundTileType(tileType: number, x: number, y: number, dx: number): number {
    if (tileType % 60 === 1) {
      return this.determineLandForegroundTileType(x, y, dx);
    }
    return this.determineWaterForegroundTileType(x, y, dx);
  }
}
