import * as SkeletonUtils from 'three/examples/jsm/utils/SkeletonUtils';

import {
  Mesh,
  MeshStandardMaterial,
  Color,
  AnimationAction,
  AnimationMixer,
  Object3D,
  Vector3,
  ShaderMaterial,
} from 'three';
import { FullStatePacket, CharacterPacket } from '../../../common/types/packet';
import { CanvasUtils } from '../canvas-utils';
import { NameTag } from '../nametag';
import { PonyModelLoader } from '../resource/pony-loader';
import { ClientWorld } from '../world/client-world';
import { PonyEyes } from './eyes';

const nameTagBuilder = new CanvasUtils({
  fill: false,
  textBorderColor: '#000',
  foregroundColor: '#fff',
  textShadowBlur: 2,
  textBorderSize: 1,
});

export class PonyEntity {
  public velocity = new Vector3(0, 0, 0);
  public angularVelocity = new Vector3(0, 0, 0);
  public onFloor = true;
  public remote = false;
  public gravity = -50;
  public jumpPower = 16;
  public mixer!: AnimationMixer;
  public container!: Object3D;
  public model!: Object3D;
  public material!: MeshStandardMaterial;
  public walkAnimationState = 0;
  public idleAction: AnimationAction;
  public walkAction: AnimationAction;
  public nameTag?: NameTag;
  public changes: FullStatePacket = {};
  public heightSource?: ClientWorld;

  initialize() {
    const loader = PonyModelLoader.getInstance();
    this.model = (SkeletonUtils as any).clone(loader.ponyModel);
    this.material = (
      (this.model.children[0].children[1] as Mesh)
        .material as MeshStandardMaterial
    ).clone();
    (this.model.children[0].children[1] as Mesh).material = this.material;
    (this.model.children[0].children[2] as Mesh).material =
      PonyEyes.getInstance().getShader();
    this.mixer = new AnimationMixer(this.model);
    this.idleAction = this.mixer.clipAction(loader.animations[0]);
    this.walkAction = this.mixer.clipAction(loader.animations[2]);
    this.idleAction.play();
    this.container = new Object3D();
    this.container.add(this.model);
  }

  update(dt: number) {
    this.mixer.update(dt);

    if (this.remote) {
      return;
    }

    this.velocity.y += this.gravity * dt;

    this.container.position.add(this.velocity.clone().multiplyScalar(dt));
    this.container.rotation.setFromVector3(
      new Vector3(
        this.container.rotation.x,
        this.container.rotation.y,
        this.container.rotation.z,
      ).add(this.angularVelocity.clone().multiplyScalar(dt)),
    );

    // Put pony on the terrain
    const terrainFloorHeight =
      this.heightSource?.getHeight(
        this.container.position.x,
        this.container.position.z,
      ) || 0;

    if (this.container.position.y <= terrainFloorHeight) {
      this.onFloor = true;
      this.velocity.y = 0;
      this.container.position.y = terrainFloorHeight;
    } else {
      this.onFloor = false;
    }
  }

  dispose() {
    this.model = null;
    this.material.dispose();
    this.nameTag?.dispose();
  }

  public addNameTag(name: string) {
    this.nameTag = new NameTag(nameTagBuilder, name);
    this.nameTag.tag.position.set(0, 1.8, 0.5);
    this.container.add(this.nameTag.tag);
  }

  public setWalkAnimationState(index: number) {
    const previousState = this.walkAnimationState;
    this.walkAnimationState = index;
    if (previousState === this.walkAnimationState) {
      return;
    }

    this.changes.animState = index;

    if (index === 1) {
      this.walkAction.reset().crossFadeFrom(this.idleAction, 0.5, false).play();
    } else {
      this.walkAction.crossFadeTo(this.idleAction.reset(), 0.5, false).play();
    }
  }

  public jump() {
    if (!this.onFloor) {
      return;
    }

    this.velocity.y = 12;
  }

  public setColor(color: number | string) {
    this.material.color = new Color(color);
  }

  public setEyeColor(color: number | string) {
    PonyEyes.getInstance().setColor(
      (this.model.children[0].children[2] as Mesh).material as ShaderMaterial,
      color,
    );
  }

  public setCharacter(packet: CharacterPacket) {
    if (packet.color) {
      this.setColor(packet.color);
    }

    if (packet.eyeColor) {
      this.setEyeColor(packet.eyeColor);
    }
  }

  public setHeightSource(source: ClientWorld) {
    this.heightSource = source;
  }
}
