using System.Collections;
using System.Collections.Generic;
using UnityEngine;

namespace Machete {
  public enum CommandType {
    Wait,
    MoveUp,
    MoveDown,
    MoveLeft,
    MoveRight
  }

	public class Message : MonoBehaviour {
    [System.Serializable]
    public struct Command {
      public CommandType type;
      public Desk desk;
    }

    List<Command> commands;

    [SerializeField]
    int commandsPerTick;
    int ticksCount = 0;

    public Cheater Sender {
      get { return sender; }
      set {
        if (originalSender == null) {
          originalSender = (PlayableCheater)value;
        }
        sender = value;
        originalDesk = value.Desk;
      }
    }
    Cheater sender;
    PlayableCheater originalSender;

    public Cheater Receiver {
      get { return receiver; }
      set { receiver = value; }
    }
    Cheater receiver;

    public bool Resolved {
      get { return resolved; }
      set { resolved = value; }
    }
    bool resolved = false;

    public Desk Desk {
      get { return desk; }
      set {
        desk = value;
        if (desk != null) {
          transform.parent = desk.transform;
          transform.position = desk.transform.position + messagePosOffset;
        }
      }
    }

    [SerializeField]
    private Vector3 messagePosOffset;

    Desk desk;
    Desk originalDesk;


    public bool IsValidPlan {
      get { return commands.Count != 0 && commands[commands.Count - 1].desk.Student == receiver; }
    }
    public bool Planning {
      get { return planning; }
      set { planning = value; }
    }
    bool planning = true;

    void Start() {
      commands = new List<Command>();
    }

    public void MarkEndPoints(bool active) {
      sender.MarkAsSender(active);
      receiver.MarkAsReceiver(active);
    }

    public void Execute() {
      // Only execute if valid.
      bool isValid = commands[commands.Count - 1].desk.Student is Cheater;
      if (!isValid) {
        return;
      }
      // Move to original Desk.
      desk.Message = null;
      originalDesk.Message = this;

      // Set as executing.
      planning = false;
      MarkEndPoints(false);
      // Advance on ticker.
      Ticker.Instance.OnTick += AdvancePlan;
    }

    void AdvancePlan() {
      // Check if it is time to advance.
      ticksCount++;
      if (commandsPerTick - 1 > ticksCount) {
        return;
      }
      // Execute.
      ticksCount = 0;
      Command command = commands[0];
      commands.RemoveAt(0);
      // Command wait exit.
      if (command.type == CommandType.Wait) {
        return;
      }

      // Check if caught.
      if (command.desk.InCheck) {
        Destroy(gameObject);
        Ticker.Instance.OnTick -= AdvancePlan;
        commands.Clear();
        originalSender.MessageCaught();
      }

      if (command.desk.Student.TakeMessage(this)) {
        // Move to desk.
        desk.Message = null;
        command.desk.Message = this;
      } else {
        Destroy(gameObject);
        Ticker.Instance.OnTick -= AdvancePlan;
        commands.Clear();
        originalSender.MessageLost();
      }

      // Check if we are done.
      if (commands.Count == 0) {
        Ticker.Instance.OnTick -= AdvancePlan;
      }
    }

    public bool InteractWithDesk(Desk target) {
      // If target is the same as current desk add a wait.
      if (target == desk) {
        AddCommand(CommandType.Wait, target);
        return false;
      }

      // Check if desk was in the command list.
      if (ResetToCommand(target)) {
        return target.Student is Cheater;
      }

      // Check if desk is neighbour of the current desk.
      GridDirection direction = desk.IsNeighbor(target);
      if (direction != GridDirection.NoNeighbor) {
        // Send move command.
        CommandType type = CommandType.MoveDown;
        switch(direction) {
          case GridDirection.Up:
            type = CommandType.MoveUp;
            break;
          case GridDirection.Right:
            type = CommandType.MoveRight;
            break;
          case GridDirection.Down:
            type = CommandType.MoveDown;
            break;
          case GridDirection.Left:
            type = CommandType.MoveLeft;
            break;
        }
        AddCommand(type, target);
      }

      return target.Student is Cheater;
    }

    public void AddCommand(CommandType type, Desk toDesk) {
      // Create command
      Command command = new Command();
      command.type = type;
      command.desk = toDesk;
      // Add to list. // TODO: update counts?
      commands.Add(command);
      // Update desk if it changed.
      if (toDesk != desk) {
        desk.Message = null;
        toDesk.Message = this;
      }
    }

    public void RemoveLast(CommandType type, Desk desk) {
      Command lastCommand = commands[commands.Count - 1];
      if (lastCommand.type == type && lastCommand.desk == desk) {
        commands.RemoveAt(commands.Count - 1); // TODO: update counts?
      }
    }

    public bool ResetToCommand(Desk toDesk) {
      // Check if clicking on original desk.
      if (toDesk == originalDesk) {
        commands.Clear();
        desk.Message = null;
        originalDesk.Message = this;
        return false;
      }

      // Go to first command of a desk.
      if (toDesk == desk) {
        return false;
      }

      // Find first appearance of desk and then keep until desk changes again.
      bool found = false;
      for (var i = 0; i < commands.Count; i++) {
        if (!found && commands[i].desk == toDesk){
          found = true;
          continue;
        } else if (found && commands[i].desk != toDesk) {
          commands.RemoveRange(i, commands.Count - i);
          desk.Message = null;
          toDesk.Message = this;
        }
      }

      return found;
    }
	}
}
