import {Resource, Texture} from "@pixi/core";

import {GetAssetManager, IGraphicAsset, IObjectVisualizationData, IRoomGeometry, RoomObjectVariable, RoomObjectVisualizationType} from "../../../../../api";
import {AnimationData, AnimationStateData, DirectionData, LayerData} from "../data";
import {FurnitureAnimatedVisualization, FurnitureVisualizationData} from "../furniture";
import {ExperienceData} from "./ExperienceData";
import {PetVisualizationData} from "./PetVisualizationData";

export class PetVisualization extends FurnitureAnimatedVisualization {
  public static override TYPE: string = RoomObjectVisualizationType.PET_ANIMATED;

  private static HEAD: string = "head";
  private static SADDLE: string = "saddle";
  private static HAIR: string = "hair";
  private static ADDITIONAL_SPRITE_COUNT: number = 1;
  private static EXPERIENCE_BUBBLE_VISIBLE_IN_MS: number = 1000;
  private static PET_EXPERIENCE_BUBBLE: string = "avatar_addition_pet_experience_bubble";
  private static POSTURE_ANIMATION_INDEX: number = 0;
  private static GESTURE_ANIMATION_INDEX: number = 1;
  private static ANIMATION_INDEX_COUNT: number = 2;

  private _posture: string;
  private _gesture: string;
  private _isSleeping: boolean;
  private _headDirection: number;
  private _headOnly: boolean;
  private _nonHeadSprites: boolean[];
  private _headSprites: boolean[];
  private _saddleSprites: boolean[];
  private _animationOver: boolean;
  private _paletteIndex: number;
  private _paletteName: string;
  private _customLayerIds: number[];
  private _customPartIds: number[];
  private _customPaletteIds: number[];
  private _isRiding: boolean;
  private _color: number;
  private _experience: number;
  private _experienceTimestamp: number;
  private _experienceData: ExperienceData;

  private _previousAnimationDirection: number;
  private _animationStates: AnimationStateData[];

  constructor() {
    super();

    this._posture = "";
    this._gesture = "";
    this._isSleeping = false;
    this._headDirection = -1;
    this._headOnly = false;
    this._nonHeadSprites = [];
    this._headSprites = [];
    this._saddleSprites = [];
    this._animationOver = false;
    this._paletteIndex = -1;
    this._paletteName = "";
    this._customLayerIds = [];
    this._customPartIds = [];
    this._customPaletteIds = [];
    this._isRiding = false;
    this._color = 0xffffff;
    this._experience = 0;
    this._experienceTimestamp = 0;
    this._experienceData = null;

    this._previousAnimationDirection = -1;
    this._animationStates = [];

    while (this._animationStates.length < PetVisualization.ANIMATION_INDEX_COUNT) this._animationStates.push(new AnimationStateData());
  }

  public override initialize(data: IObjectVisualizationData): boolean {
    if (!(data instanceof PetVisualizationData)) return false;

    const texture = this.getPetAdditionAsset(PetVisualization.PET_EXPERIENCE_BUBBLE);

    if (texture) {
      this._experienceData = new ExperienceData(texture);
    }

    return super.initialize(data);
  }

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

    if (this._animationStates) {
      while (this._animationStates.length) {
        const animationState = this._animationStates[0];

        if (animationState) animationState.dispose();

        this._animationStates.pop();
      }

      this._animationStates = null;
    }
  }

  protected override getAnimationId(animationData: AnimationStateData): number {
    return animationData.animationId;
  }

  public override update(geometry: IRoomGeometry, time: number, update: boolean, skipUpdate: boolean): void {
    super.update(geometry, time, update, skipUpdate);

    this.updateExperienceBubble(time);
  }

  protected updateExperienceBubble(time: number): void {
    if (!this._experienceData) return;

    this._experienceData.alpha = 0;

    if (this._experienceTimestamp) {
      const difference = time - this._experienceTimestamp;

      if (difference < PetVisualization.EXPERIENCE_BUBBLE_VISIBLE_IN_MS) {
        this._experienceData.alpha = Math.sin((difference / PetVisualization.EXPERIENCE_BUBBLE_VISIBLE_IN_MS) * Math.PI) * 0xff;
      } else {
        this._experienceTimestamp = 0;
      }

      const sprite = this.getSprite(this.totalSprites - 1);

      if (sprite) {
        if (this._experienceData.alpha > 0) {
          const texture = this._experienceData.renderBubble(this._experience);

          if (texture) {
            sprite.texture = texture;
            sprite.offsetX = -20;
            sprite.offsetY = -80;
            sprite.alpha = this._experienceData.alpha;
            sprite.visible = true;
            sprite.relativeDepth = -0.2;

            return;
          }
        }

        sprite.texture = null;
        sprite.visible = false;
      }
    }
  }

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

    if (!model) return false;

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

    const posture = model.getValue<string>(RoomObjectVariable.FIGURE_POSTURE);
    const gesture = model.getValue<string>(RoomObjectVariable.FIGURE_GESTURE);

    this.setPostureAndGesture(posture, gesture);

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

    if (alphaMultiplier === null || isNaN(alphaMultiplier)) alphaMultiplier = 1;

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

      this._alphaChanged = true;
    }

    this._isSleeping = model.getValue<number>(RoomObjectVariable.FIGURE_SLEEP) > 0;

    const headDirection = model.getValue<number>(RoomObjectVariable.HEAD_DIRECTION);

    if (!isNaN(headDirection) && this.data.isAllowedToTurnHead) {
      this._headDirection = headDirection;
    } else {
      this._headDirection = this.object.getDirection().x;
    }

    this._experience = model.getValue<number>(RoomObjectVariable.FIGURE_GAINED_EXPERIENCE);
    this._experienceTimestamp = model.getValue<number>(RoomObjectVariable.FIGURE_EXPERIENCE_TIMESTAMP);

    const customPaletteIndex = model.getValue<number>(RoomObjectVariable.PET_PALETTE_INDEX);
    const customLayerIds = model.getValue<number[]>(RoomObjectVariable.PET_CUSTOM_LAYER_IDS);
    const customPartIds = model.getValue<number[]>(RoomObjectVariable.PET_CUSTOM_PARTS_IDS);
    const customPaletteIds = model.getValue<number[]>(RoomObjectVariable.PET_CUSTOM_PALETTE_IDS);
    const isRiding = model.getValue<number>(RoomObjectVariable.PET_IS_RIDING);
    const headOnly = model.getValue<number>(RoomObjectVariable.PET_HEAD_ONLY);
    const color = model.getValue<number>(RoomObjectVariable.PET_COLOR);

    if (customPaletteIndex !== this._paletteIndex) {
      this._paletteIndex = customPaletteIndex;
      this._paletteName = this._paletteIndex.toString();
    }

    this._customLayerIds = customLayerIds ? customLayerIds : [];
    this._customPartIds = customPartIds ? customPartIds : [];
    this._customPaletteIds = customPaletteIds ? customPaletteIds : [];
    this._isRiding = !isNaN(isRiding) && isRiding > 0;
    this._headOnly = !isNaN(headOnly) && headOnly > 0;

    if (!isNaN(color) && this._color !== color) this._color = color;

    this.updateModelCounter = model.updateCounter;

    return true;
  }

  protected override updateAnimation(scale: number): number {
    if (this.object) {
      const direction = this.object.getDirection().x;

      if (direction !== this._previousAnimationDirection) {
        this._previousAnimationDirection = direction;

        this.resetAllAnimationFrames();
      }
    }

    return super.updateAnimation(scale);
  }

  protected setPostureAndGesture(posture: string, gesture: string): void {
    if (!this.data) return;

    if (posture !== this._posture) {
      this._posture = posture;

      this.setAnimationForIndex(PetVisualization.POSTURE_ANIMATION_INDEX, this.data.postureToAnimation(this._scale, posture));
    }

    if (this.data.getGestureDisabled(this._scale, posture)) gesture = null;

    if (gesture !== this._gesture) {
      this._gesture = gesture;

      this.setAnimationForIndex(PetVisualization.GESTURE_ANIMATION_INDEX, this.data.gestureToAnimation(this._scale, gesture));
    }
  }

  private getAnimationStateData(k: number): AnimationStateData {
    if (k >= 0 && k < this._animationStates.length) return this._animationStates[k];

    return null;
  }

  private setAnimationForIndex(k: number, _arg_2: number): void {
    const animationStateData = this.getAnimationStateData(k);

    if (animationStateData) {
      if (this.setSubAnimation(animationStateData, _arg_2)) this._animationOver = false;
    }
  }

  protected override resetAllAnimationFrames(): void {
    this._animationOver = false;

    let index = this._animationStates.length - 1;

    while (index >= 0) {
      const stateData = this._animationStates[index];

      if (stateData) stateData.setLayerCount(this.animatedLayerCount);

      index--;
    }
  }

  protected override updateAnimations(scale: number): number {
    if (this._animationOver) return 0;

    let animationOver = true;
    let _local_3 = 0;
    let index = 0;

    while (index < this._animationStates.length) {
      const stateData = this._animationStates[index];

      if (stateData) {
        if (!stateData.animationOver) {
          const _local_6 = this.updateFramesForAnimation(stateData, scale);

          _local_3 = _local_3 | _local_6;

          if (!stateData.animationOver) {
            animationOver = false;
          } else {
            if (AnimationData.isTransitionFromAnimation(stateData.animationId) || AnimationData.isTransitionToAnimation(stateData.animationId)) {
              this.setAnimationForIndex(index, stateData.animationAfterTransitionId);

              animationOver = false;
            }
          }
        }
      }

      index++;
    }

    this._animationOver = animationOver;

    return _local_3;
  }

  protected override getSpriteAssetName(scale: number, layerId: number): string {
    if (this._headOnly && this.isNonHeadSprite(layerId)) return null;

    if (this._isRiding && this._parser3(layerId)) return null;

    const totalSprites = this.totalSprites;

    if (layerId < totalSprites - PetVisualization.ADDITIONAL_SPRITE_COUNT) {
      const validScale = this.getValidSize(scale);

      if (layerId < totalSprites - (1 + PetVisualization.ADDITIONAL_SPRITE_COUNT)) {
        if (layerId >= FurnitureVisualizationData.LAYER_LETTERS.length) return null;

        const layerLetter = FurnitureVisualizationData.LAYER_LETTERS[layerId];

        if (validScale === 1) return this._type + "_icon_" + layerLetter;

        return this._type + "_" + validScale + "_" + layerLetter + "_" + this.getDirection(scale, layerId) + "_" + this.getFrameNumber(validScale, layerId);
      }

      return this._type + "_" + validScale + "_sd_" + this.getDirection(scale, layerId) + "_0";
    }

    return null;
  }

  protected override getLayerColor(scale: number, layerId: number, colorId: number): number {
    if (layerId < this.totalSprites - PetVisualization.ADDITIONAL_SPRITE_COUNT) return this._color;

    return 0xffffff;
  }

  protected override getLayerXOffset(scale: number, direction: number, layerId: number): number {
    let offset = super.getLayerXOffset(scale, direction, layerId);
    let index = this._animationStates.length - 1;

    while (index >= 0) {
      const stateData = this._animationStates[index];

      if (stateData) {
        const frame = stateData.getFrame(layerId);

        if (frame) offset += frame.x;
      }

      index--;
    }

    return offset;
  }

  protected override getLayerYOffset(scale: number, direction: number, layerId: number): number {
    let offset = super.getLayerYOffset(scale, direction, layerId);
    let index = this._animationStates.length - 1;

    while (index >= 0) {
      const stateData = this._animationStates[index];

      if (stateData) {
        const frame = stateData.getFrame(layerId);

        if (frame) offset += frame.y;
      }

      index--;
    }

    return offset;
  }

  protected override getLayerZOffset(scale: number, direction: number, layerId: number): number {
    if (!this.data) return LayerData.DEFAULT_ZOFFSET;

    return this.data.getLayerZOffset(scale, this.getDirection(scale, layerId), layerId);
  }

  private getDirection(scale: number, layerId: number): number {
    if (!this.isHeadSprite(layerId)) return this._direction;

    return this.data.getValidDirection(scale, this._headDirection);
  }

  protected override getFrameNumber(scale: number, layerId: number): number {
    let index = this._animationStates.length - 1;

    while (index >= 0) {
      const stateData = this._animationStates[index];

      if (stateData) {
        const frame = stateData.getFrame(layerId);

        if (frame) return frame.id;
      }

      index--;
    }

    return super.getFrameNumber(scale, layerId);
  }

  private isHeadSprite(layerId: number): boolean {
    if (this._headSprites[layerId] === undefined) {
      const isHead = this.data.getLayerTag(this._scale, DirectionData.USE_DEFAULT_DIRECTION, layerId) === PetVisualization.HEAD;
      const isHair = this.data.getLayerTag(this._scale, DirectionData.USE_DEFAULT_DIRECTION, layerId) === PetVisualization.HAIR;

      if (isHead || isHair) this._headSprites[layerId] = true;
      else this._headSprites[layerId] = false;
    }

    return this._headSprites[layerId];
  }

  private isNonHeadSprite(layerId: number): boolean {
    if (this._nonHeadSprites[layerId] === undefined) {
      if (layerId < this.totalSprites - (1 + PetVisualization.ADDITIONAL_SPRITE_COUNT)) {
        const tag = this.data.getLayerTag(this._scale, DirectionData.USE_DEFAULT_DIRECTION, layerId);

        if (tag && tag.length > 0 && tag !== PetVisualization.HEAD && tag !== PetVisualization.HAIR) {
          this._nonHeadSprites[layerId] = true;
        } else {
          this._nonHeadSprites[layerId] = false;
        }
      } else {
        this._nonHeadSprites[layerId] = true;
      }
    }

    return this._nonHeadSprites[layerId];
  }

  private _parser3(layerId: number): boolean {
    if (this._saddleSprites[layerId] === undefined) {
      if (this.data.getLayerTag(this._scale, DirectionData.USE_DEFAULT_DIRECTION, layerId) === PetVisualization.SADDLE) {
        this._saddleSprites[layerId] = true;
      } else {
        this._saddleSprites[layerId] = false;
      }
    }

    return this._saddleSprites[layerId];
  }

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

    const layerIndex = this._customLayerIds.indexOf(layerId);
    let paletteName = this._paletteName;
    let partId = -1;
    let paletteId = -1;

    if (layerIndex > -1) {
      partId = this._customPartIds[layerIndex];
      paletteId = this._customPaletteIds[layerIndex];
      paletteName = paletteId > -1 ? paletteId.toString() : this._paletteName;
    }

    if (!isNaN(partId) && partId > -1) {
      name = name + "_" + partId;
    }

    return this.asset.getAssetWithPalette(name, paletteName);
  }

  protected override getAdditionalLayerCount(): number {
    return super.getAdditionalLayerCount() + PetVisualization.ADDITIONAL_SPRITE_COUNT;
  }

  protected override setLayerCount(count: number): void {
    super.setLayerCount(count);

    this._headSprites = [];
  }

  protected override getPostureForAsset(scale: number, name: string): string {
    const parts = name.split("_");
    let length = parts.length;
    let i = 0;

    while (i < parts.length) {
      if (parts[i] === "64" || parts[i] === "32") {
        length = i + 3;

        break;
      }

      i++;
    }

    let posture: string = null;

    if (length < parts.length) {
      let part = parts[length];

      part = part.split("@")[0];

      posture = this.data.animationToPosture(scale, parseInt(part) / 100, false);

      if (!posture) posture = this.data.getGestureForAnimationId(scale, parseInt(part) / 100);
    }

    return posture;
  }

  public getPetAdditionAsset(name: string): Texture<Resource> {
    return GetAssetManager().getTexture(name);
  }

  protected override get data(): PetVisualizationData {
    return this._data as PetVisualizationData;
  }
}
