
/* 
  *******************************************************************************************************************************
  DISCLAIMER: 
    This code is provided "as is" without warranty of any kind, either express or implied, including but not limited to 
    the implied warranties of merchantability and fitness for a particular purpose. ReBo and any contributors provide 
    this code for educational and informational purposes only. Users are encouraged to freely use, modify, and distribute 
    this code for non-commercial purposes. Any commercial use of this code or derivative works thereof is strictly prohibited 
    unless explicit permission is obtained from ReBo and any contributors.
  ******************************************************************************************************************************* 
  */

import {utils} from 'Utils';
import {system} from '@minecraft/server';

export class Vector2 {
  constructor(x = 0, y = 0) {
    this.x = x;
    this.y = y;
  }
  toString() {
    return `${this.x} ${this.y}`;
  }
}

export class Vector3 {
  constructor(x = 0, y = 0, z = 0) {
    this.x = x;
    this.y = y;
    this.z = z;
  }
  toVector2() {
    return new Vector2(this.x, this.y);
  }
  toString() {
    return `${this.x} ${this.y} ${this.z}`;
  }
}
export class Checkpoint {
  constructor(entity) {
    this.entity = entity;
    this.x = entity.location.x;
    this.y = entity.location.y;
    this.z = entity.location.z;
    this.rx = entity.getRotation().x;
    this.ry = entity.getRotation().y;
    this.fx = entity.getViewDirection().x;
    this.fy = entity.getViewDirection().y;
    this.fz = entity.getViewDirection().z;
  }
  return() {
    return utils.entityCommandAsync(
      this.entity,
      `teleport @s ${this.x} ${this.y} ${this.z} ${this.ry} ${this.rx}`
    );
  }
}

export class Fade {
  /**
   * @param {float} fadeIn - Fade in time in seconds.
   * @param {float} fadeHold - Fade hold time in seconds.
   * @param {float} fadeOut - Fade out time in seconds.
   */
  constructor(fadeIn, fadeHold, fadeOut) {
    this.fadeIn = fadeIn;
    this.fadeHold = fadeHold;
    this.fadeOut = fadeOut;
    this.duration = fadeIn + fadeHold + fadeOut;
    this.fadeInTick = fadeIn * 20;
    this.fadeHoldTick = fadeHold * 20;
    this.fadeOutTick = fadeOut * 20;
    this.durationTick = (fadeIn + fadeHold + fadeOut) * 20;
  }
}

export class Scene {
  /**
   * @param {Vector3|String} start - Starting point of the scene.
   * @param {Vector3|String} end - Ending point of the scene.
   * @param {string} facing - Coordinates or Selector of the subject of the scene.
   * @param {float} duration - Time in seconds for the camera to travel from start to end point.
   * @param {enum} ease - Ease type the scene will use.
   */
  constructor(start, end, facing, duration, ease = "linear", fade) {
    if (typeof start == "Vector3") {
      start = start.toString();
    }
    if (typeof end == "string") { 
      let location = end.split(' ');
      end = new Vector3(location[0], location[1], location[2])
    }
    if (typeof end == "Vector3") {
      end.y += 1.75; // Actual camera y-coordinate from player view.
      end = end.toString();
    }
    this.start = start;
    this.end = end;
    this.facing = facing;
    this.duration = duration;
    this.durationTick = duration * 20;
    this.ease = ease;
    this.fade = fade;
  }
}

export class TimedCommand {
  /**
   * @param {float} time - Time in seconds when to execute commands.
   * @param {String|String[]} command - Commands to run in specified time.
   */
  constructor(time, command) {
    this.time = time;
    this.timeTick = time * 20;
    this.command = command;
  }
}


export class Cutscene {
  /**
   * @param {string} name - Name and identifier of the cutscene.
   * @param {string} selector - The target entities to apply the scenes with.
   * @param {Scene[]} scenes - Scenes to play in order.
   * @param {bool} spectator - Option to use spectator mode in the cutscene.
   * @param {string[]} startFunction - Functions to run before playing the cutscene.
   * @param {string[]} endFunction - Functions to run after playing the cutscene.
   * @param {TimedCommand[]} timeline - Timeline of commands to be inserted.
   */

  checkpoints = [];
  constructor(
    name,
    selector,
    scenes,
    spectator = true,
    startFunction = ["testfor @s"],
    endFunction = ["testfor @s"],
    timeline
  ) {
    this.name = name;
    this.selector = selector;
    this.scenes = scenes;
    this.spectator = spectator;
    this.startFunction = startFunction;
    this.endFunction = endFunction;
    this.timeline = timeline;

    system.runInterval(() => {
      const runTrigger = utils.getEntities({
        type: "player",
        tags: [name],
      });

      if (runTrigger[0]) {
        this.run();
      }
    });
  }
  run() {
    if (this.spectator) {
      utils.serverCommandAsync(
        `execute as ${this.selector} if entity @s[m=adventure] run tag @s add adventure`,
        `execute as ${this.selector} if entity @s[m=creative] run tag @s add creative`,
        `execute as ${this.selector} if entity @s[m=survival] run tag @s add survival`,
        `gamemode spectator ${this.selector}`
      );
    }

    const entities = utils.getEntities(this.selector);
    entities.forEach((entity) => {
      this.checkpoints.push(new Checkpoint(entity));
    });

    utils.serverCommandAsync(`tag @a remove ${this.name}`, this.startFunction);
    // Execute Timed commands.
    this.timeline.forEach((timedCommand) => {
      system.runTimeout(() => {
        utils.serverCommandAsync(timedCommand.command)
      },timedCommand.timeTick )
    });
    // Execute scenes
    let delay = 0;
    this.scenes.forEach((scene, i) => {
      if (scene.fade) {
        system.runTimeout(() => {
          utils.serverCommandAsync(
            `camera ${this.selector} fade time ${scene.fade.fadeIn} ${scene.fade.fadeHold} ${scene.fade.fadeOut}`
          );
        }, delay);
      }

      delay += scene.fade ? scene.fade.fadeInTick : 0;

      system.runTimeout(() => {
        utils.serverCommandAsync(
          `camera ${this.selector} clear`,
          `teleport ${this.selector} ${scene.start} facing ${
            scene.facing
          }`,
        );
      }, delay);

      delay += 1 + (scene.fade ? scene.fade.fadeInTick + (scene.fade.fadeHoldTick - 20) : 0);

      system.runTimeout(() => {
        utils.serverCommandAsync(
          `camera ${this.selector} clear`,
          `camera ${this.selector} set minecraft:free ease ${scene.duration} ${
            scene.ease
          } pos ${scene.end} facing ${
            scene.facing
          }`
        );
      }, delay);

      delay += scene.durationTick;

      if (i + 1 === this.scenes.length) {
        system.runTimeout(() => {
          this.stop();
        }, delay + 1);
      }
    });
  }
  stop() {
    this.checkpoints.forEach((checkpoint) => {
      checkpoint.return();
    });
    if (this.spectator) {
      utils.serverCommandAsync(
        `execute as ${this.selector} if entity @s[tag=adventure] run gamemode adventure ${this.selector}`,
        `execute as ${this.selector} if entity @s[tag=adventure] run tag @s remove adventure`,
        `execute as ${this.selector} if entity @s[tag=creative] run gamemode creative ${this.selector}`,
        `execute as ${this.selector} if entity @s[tag=creative] run tag @s remove creative`,
        `execute as ${this.selector} if entity @s[tag=survival] run gamemode survival ${this.selector}`,
        `execute as ${this.selector} if entity @s[tag=survival] run tag @s remove survival`
      );
    }
    utils.serverCommandAsync(`camera ${this.selector} clear`, this.endFunction);
  }
}