import {
  AlphaTolerance,
  IGraphicAsset,
  IObjectVisualizationData,
  IRoomGeometry,
  IRoomObjectSprite,
  RoomObjectVariable,
  RoomObjectVisualizationType,
} from "../../../../../api";
import {RoomObjectSpriteVisualization} from "../../../../../room";
import {ColorData, LayerData} from "../data";
import {FurnitureVisualizationData} from "./FurnitureVisualizationData";

export class FurnitureVisualization extends RoomObjectSpriteVisualization {
  protected static DEPTH_MULTIPLIER: number = Math.sqrt(0.5);

  public static TYPE: string = RoomObjectVisualizationType.FURNITURE_STATIC;

  protected _data: FurnitureVisualizationData;

  protected _type: string;
  protected _direction: number;
  protected _lastCameraAngle: number;
  protected _selectedColor: number;
  protected _furnitureLift: number;
  protected _alphaMultiplier: number;
  protected _alphaChanged: boolean;
  protected _clickUrl: string;
  protected _clickHandling: boolean;

  protected _cacheDirection: number;
  protected _cacheScale: number;
  protected _cacheSize: number;

  protected _layerCount: number;
  protected _shadowLayerIndex: number;
  protected _updatedLayers: boolean[];
  protected _assetNames: string[];
  protected _spriteTags: string[];
  protected _spriteInks: number[];
  protected _spriteAlphas: number[];
  protected _spriteColors: number[];
  protected _spriteMouseCaptures: boolean[];
  protected _spriteXOffsets: number[];
  protected _spriteYOffsets: number[];
  protected _spriteZOffsets: number[];

  private _animationNumber: number;

  constructor() {
    super();

    this._data = null;

    this._type = null;
    this._direction = 0;
    this._lastCameraAngle = NaN;
    this._selectedColor = 0;
    this._furnitureLift = 0;
    this._alphaMultiplier = 1;
    this._alphaChanged = false;
    this._clickUrl = null;
    this._clickHandling = false;

    this._cacheDirection = -1;
    this._cacheScale = 0;
    this._cacheSize = -1;

    this._layerCount = 0;
    this._shadowLayerIndex = -1;
    this._updatedLayers = [];
    this._assetNames = [];
    this._spriteTags = [];
    this._spriteInks = [];
    this._spriteAlphas = [];
    this._spriteColors = [];
    this._spriteMouseCaptures = [];
    this._spriteXOffsets = [];
    this._spriteYOffsets = [];
    this._spriteZOffsets = [];

    this._animationNumber = 0;
  }

  public override initialize(data: IObjectVisualizationData): boolean {
    this.reset();

    if (!(data instanceof FurnitureVisualizationData)) return false;

    this._type = data.type;
    this._data = data;

    return true;
  }

  public override dispose(): void {
    super.dispose();

    this._data = null;
    this._updatedLayers = null;
    this._assetNames = null;
    this._spriteTags = null;
    this._spriteInks = null;
    this._spriteAlphas = null;
    this._spriteColors = null;
    this._spriteMouseCaptures = null;
    this._spriteXOffsets = null;
    this._spriteYOffsets = null;
    this._spriteZOffsets = null;
  }

  protected override reset(): void {
    super.reset();

    this.setDirection(-1);

    this._data = null;
    this._updatedLayers = [];
    this._assetNames = [];
    this._spriteTags = [];
    this._spriteInks = [];
    this._spriteAlphas = [];
    this._spriteColors = [];
    this._spriteMouseCaptures = [];
    this._spriteXOffsets = [];
    this._spriteYOffsets = [];
    this._spriteZOffsets = [];

    this.createSprites(0);
  }

  protected resetLayers(scale: number, direction: number): void {
    if (this._cacheDirection === direction && this._cacheScale === scale) return;

    this._updatedLayers = [];
    this._assetNames = [];
    this._spriteTags = [];
    this._spriteInks = [];
    this._spriteAlphas = [];
    this._spriteColors = [];
    this._spriteMouseCaptures = [];
    this._spriteXOffsets = [];
    this._spriteYOffsets = [];
    this._spriteZOffsets = [];

    this._cacheDirection = direction;
    this._cacheScale = scale;
    this._cacheSize = this.getValidSize(scale);

    this.setLayerCount(((this._data && this._data.getLayerCount(scale)) || 0) + this.getAdditionalLayerCount());
  }

  public override update(geometry: IRoomGeometry, time: number, update: boolean, skipUpdate: boolean): void {
    if (!geometry) return;

    const scale = geometry.scale;
    let updateSprites = false;

    if (this.updateObject(scale, geometry.direction.x)) updateSprites = true;

    if (this.updateModel(scale)) updateSprites = true;

    let number = 0;

    if (skipUpdate) {
      this._animationNumber = this._animationNumber | this.updateAnimation(scale);
    } else {
      number = this.updateAnimation(scale) | this._animationNumber;

      this._animationNumber = 0;
    }

    if (updateSprites || number !== 0) {
      this.updateSprites(scale, updateSprites, number);

      this._scale = scale;

      this.updateSpriteCounter++;
    }
  }

  protected updateObject(scale: number, direction: number): boolean {
    if (!this.object) return false;

    if (this.updateObjectCounter === this.object.updateCounter && scale === this._scale && this._lastCameraAngle === direction) return false;

    let offsetDirection = this.object.getDirection().x - (direction + 135);

    offsetDirection = ((offsetDirection % 360) + 360) % 360;

    if (this._data) {
      const validDirection = this._data.getValidDirection(scale, offsetDirection);

      this.setDirection(validDirection);
    }

    this._lastCameraAngle = direction;
    this._scale = scale;

    this.updateObjectCounter = this.object.updateCounter;

    this.resetLayers(scale, this._direction);

    return true;
  }

  protected updateModel(scale: number): boolean {
    const model = this.object && this.object.model;

    if (!model) return false;

    if (this.updateModelCounter === model.updateCounter) return false;

    this._selectedColor = model.getValue<number>(RoomObjectVariable.FURNITURE_COLOR);
    this._clickUrl = model.getValue<string>(RoomObjectVariable.FURNITURE_AD_URL);
    this._clickHandling = (this._clickUrl && this._clickUrl !== "" && this._clickUrl.indexOf("http") === 0) || false;
    this._furnitureLift = model.getValue<number>(RoomObjectVariable.FURNITURE_LIFT_AMOUNT) || 0;

    let alphaMultiplier = model.getValue<number>(RoomObjectVariable.FURNITURE_ALPHA_MULTIPLIER);

    if (isNaN(alphaMultiplier)) alphaMultiplier = 1;

    if (this._alphaMultiplier !== alphaMultiplier) {
      this._alphaMultiplier = alphaMultiplier;

      this._alphaChanged = true;
    }

    this.updateModelCounter = model.updateCounter;

    return true;
  }

  protected updateSprites(scale: number, update: boolean, animation: number): void {
    if (this._layerCount !== this.totalSprites) this.createSprites(this._layerCount);

    if (update) {
      let layerId = this.totalSprites - 1;

      while (layerId >= 0) {
        this.updateSprite(scale, layerId);

        layerId--;
      }
    } else {
      let layerId = 0;

      while (animation > 0) {
        if (animation) this.updateSprite(scale, layerId);

        layerId++;
        animation = animation >> 1;
      }
    }

    this._alphaChanged = false;
  }

  protected updateSprite(scale: number, layerId: number): void {
    const assetName = this.getSpriteAssetName(scale, layerId);
    const sprite = this.getSprite(layerId);

    if (assetName && sprite) {
      const assetData = this.getAsset(assetName, layerId);

      if (assetData && assetData.texture) {
        sprite.visible = true;
        sprite.type = this._type;
        sprite.texture = assetData.texture;
        sprite.flipH = assetData.flipH;
        sprite.flipV = assetData.flipV;
        sprite.direction = this._direction;

        let relativeDepth = 0;

        if (layerId !== this._shadowLayerIndex) {
          sprite.tag = this.getLayerTag(scale, this._direction, layerId);
          sprite.alpha = this.getLayerAlpha(scale, this._direction, layerId);
          sprite.color = this.getLayerColor(scale, layerId, this._selectedColor);
          sprite.offsetX = assetData.offsetX + this.getLayerXOffset(scale, this._direction, layerId);
          sprite.offsetY = assetData.offsetY + this.getLayerYOffset(scale, this._direction, layerId);
          sprite.blendMode = this.getLayerInk(scale, this._direction, layerId);
          sprite.alphaTolerance = this.getLayerIgnoreMouse(scale, this._direction, layerId) ? AlphaTolerance.MATCH_NOTHING : AlphaTolerance.MATCH_OPAQUE_PIXELS;

          relativeDepth = this.getLayerZOffset(scale, this._direction, layerId);
          relativeDepth = relativeDepth - layerId * 0.001;
        } else {
          sprite.offsetX = assetData.offsetX;
          sprite.offsetY = assetData.offsetY + this.getLayerYOffset(scale, this._direction, layerId);
          sprite.alpha = 48 * this._alphaMultiplier;
          sprite.alphaTolerance = AlphaTolerance.MATCH_NOTHING;

          relativeDepth = 1;
        }

        sprite.relativeDepth = relativeDepth * FurnitureVisualization.DEPTH_MULTIPLIER;
        sprite.name = assetName;
        sprite.libraryAssetName = this.getLibraryAssetNameForSprite(assetData, sprite);
        sprite.posture = this.getPostureForAsset(scale, assetData.source);
        sprite.clickHandling = this._clickHandling;
      } else {
        this.resetSprite(sprite);
      }
    } else {
      if (sprite) this.resetSprite(sprite);
    }
  }

  protected getLibraryAssetNameForSprite(asset: IGraphicAsset, sprite: IRoomObjectSprite): string {
    return asset.source;
  }

  protected getPostureForAssetFile(scale: number, _arg_2: string): string {
    return null;
  }

  private resetSprite(sprite: IRoomObjectSprite): void {
    if (!sprite) return;

    sprite.texture = null;
    sprite.libraryAssetName = "";
    sprite.posture = "";
    sprite.tag = "";
    sprite.offsetX = 0;
    sprite.offsetY = 0;
    sprite.flipH = false;
    sprite.flipV = false;
    sprite.relativeDepth = 0;
    sprite.clickHandling = false;
  }

  protected getSpriteAssetName(scale: number, layerId: number): string {
    if (!this._data || layerId >= FurnitureVisualizationData.LAYER_LETTERS.length) return "";

    let assetName = this._assetNames[layerId];
    let updated = this._updatedLayers[layerId];

    if (!assetName || !assetName.length) {
      assetName = this.cacheSpriteAssetName(scale, layerId, true);
      updated = this._cacheSize !== 1;
    }

    if (updated) assetName += this.getFrameNumber(scale, layerId);

    return assetName;
  }

  protected cacheSpriteAssetName(scale: number, layerId: number, cache: boolean): string {
    const type = this._type;
    const size = cache ? this._cacheSize : this.getValidSize(scale);
    let layerCode = "";
    const isntIcon = size !== 1;

    if (layerId !== this._shadowLayerIndex) {
      layerCode = FurnitureVisualizationData.LAYER_LETTERS[layerId] || "";
    } else {
      layerCode = "sd";
    }

    if (layerCode === "") return null;

    const assetName = this._type + (isntIcon ? "_" + size + "_" + layerCode + "_" + this._direction + "_" : "_icon_" + layerCode);

    if (cache) {
      this._assetNames[layerId] = assetName;
      this._updatedLayers[layerId] = isntIcon;
    }

    return assetName;
  }

  protected getLayerTag(scale: number, direction: number, layerId: number): string {
    const existing = this._spriteTags[layerId];

    if (existing !== undefined) return existing;

    if (!this._data) return LayerData.DEFAULT_TAG;

    const tag = this._data.getLayerTag(scale, direction, layerId);

    this._spriteTags[layerId] = tag;

    return tag;
  }

  protected getLayerInk(scale: number, direction: number, layerId: number): number {
    const existing = this._spriteInks[layerId];

    if (existing !== undefined) return existing;

    if (!this._data) return LayerData.DEFAULT_INK;

    const ink = this._data.getLayerInk(scale, direction, layerId);

    this._spriteInks[layerId] = ink;

    return ink;
  }

  protected getLayerAlpha(scale: number, direction: number, layerId: number): number {
    if (!this._alphaChanged) {
      const existing = this._spriteAlphas[layerId];

      if (existing !== undefined) return existing;
    }

    if (!this._data) return LayerData.DEFAULT_ALPHA;

    let alpha = this._data.getLayerAlpha(scale, direction, layerId);

    if (this._alphaMultiplier !== null) alpha = alpha * this._alphaMultiplier;

    this._spriteAlphas[layerId] = alpha;

    return alpha;
  }

  protected getLayerColor(scale: number, layerId: number, colorId: number): number {
    const existing = this._spriteColors[layerId];

    if (existing !== undefined) return existing;

    if (!this._data) return ColorData.DEFAULT_COLOR;

    const color = this._data.getLayerColor(scale, layerId, colorId);

    this._spriteColors[layerId] = color;

    return color;
  }

  protected getLayerIgnoreMouse(scale: number, direction: number, layerId: number): boolean {
    const existing = this._spriteMouseCaptures[layerId];

    if (existing !== undefined) return existing;

    if (!this._data) return LayerData.DEFAULT_IGNORE_MOUSE;

    const ignoreMouse = this._data.getLayerIgnoreMouse(scale, direction, layerId);

    this._spriteMouseCaptures[layerId] = ignoreMouse;

    return ignoreMouse;
  }

  protected getLayerXOffset(scale: number, direction: number, layerId: number): number {
    const existing = this._spriteXOffsets[layerId];

    if (existing !== undefined) return existing;

    if (!this._data) return LayerData.DEFAULT_XOFFSET;

    const xOffset = this._data.getLayerXOffset(scale, direction, layerId);

    this._spriteXOffsets[layerId] = xOffset;

    return xOffset;
  }

  protected getLayerYOffset(scale: number, direction: number, layerId: number): number {
    if (layerId === this._shadowLayerIndex) return Math.ceil(this._furnitureLift * (scale / 2));

    const existing = this._spriteYOffsets[layerId];

    if (existing !== undefined) return existing;

    if (!this._data) return LayerData.DEFAULT_YOFFSET;

    const yOffset = this._data.getLayerYOffset(scale, direction, layerId);

    this._spriteYOffsets[layerId] = yOffset;

    return yOffset;
  }

  protected getLayerZOffset(scale: number, direction: number, layerId: number): number {
    const existing = this._spriteZOffsets[layerId];

    if (existing !== undefined) return existing;

    if (!this._data) return LayerData.DEFAULT_ZOFFSET;

    const zOffset = this._data.getLayerZOffset(scale, direction, layerId);

    this._spriteZOffsets[layerId] = zOffset;

    return zOffset;
  }

  protected getValidSize(scale: number): number {
    if (!this._data) return scale;

    return this._data.getValidSize(scale);
  }

  protected setLayerCount(count: number): void {
    this._layerCount = count;
    this._shadowLayerIndex = count - this.getAdditionalLayerCount();
  }

  protected setDirection(direction: number): void {
    if (this._direction === direction) return;

    this._direction = direction;
  }

  protected getAdditionalLayerCount(): number {
    return 1;
  }

  protected updateAnimation(scale: number): number {
    return 0;
  }

  protected getFrameNumber(scale: number, layerId: number): number {
    return 0;
  }

  protected getPostureForAsset(scale: number, name: string): string {
    return null;
  }

  public getAsset(name: string, layerId: number = -1): IGraphicAsset {
    if (!this.asset) return null;

    return this.asset.getAsset(name);
  }

  protected get direction(): number {
    return this._direction;
  }

  protected get data(): FurnitureVisualizationData {
    return this._data;
  }
}
