using UnityEngine;

/*
 * "Collections" sind Sammelklassen. Die brauchen wir hier
 * um Zugriff auf "IEnumerator" zu haben ... was allerdings
 * ein eher technisches Detail ist. Generell sollte
 * System.Collections immer "dabei" sein.
 */
using System.Collections;

/*
 * Dies ist ein .NET Namespace, der generische "Sammel-Klassen"
 * enthält. Wir brauchen ihn für die "Queue", in der wir die
 * einzelnen Teile der Schlange speichern.
 */
using System.Collections.Generic;

public class SnakeTailController : MonoBehaviour {

  // Referenz zum Kopf der Schlange.
  public Transform snakeHead = null;

  // Ein "Teil" des Schlangenschwanzes.
  public SnakeTailSpriteSelector snakeTailPart = null;

  // Das Vater-GameObject für die Segmente des Schlangenschwanzes
  public Transform snakeTailParts = null;

  /* 
   * Referenz auf SnakeHeadMover, um die aktuelle Richtung
   * zum Setzen des nächsten Schlangensegments zu ermitteln.
   */
  public SnakeHeadMover mover = null;

  public float partCreationDistance = 1.0F;

  // die ursprüngliche Länge der Schlange
  public int initialTailLength = 10;

  // Um wie viel wird die Schlange länger, wenn sie einen Apfel isst?
  public int tailLengthPerApple = 5;

  // die aktuelle Länge der Schlange
  private int currentTailLength = 0;

  // Das letzte Schlangenstück brauchen wir, um Kollisionen zwischen
  // Kopf und diesem Stück zu verhindern.
  private SnakeTailSpriteSelector lastPiece = null;

  // die letzte Position, um die zurückgelegte Entfernung berechnen zu können
  private Vector3 lastPosition = Vector2.zero;

  // Die letzte Richtung der Schlange (vor Drehungen).
  private int currentAngle = 0;

  // dieser Zähler dient nur dazu, die Schlangensegmente durchzuzählen
  private int counter = 0;

  /*
   * "Queue" ist eine Schlange von Objekten ... ehrlich!!! ;-)
   * Es gibt auch Stacks (ein "Haufen" von Objekten), einfache
   * Listen und verschiedene andere "Sammlungen" (="Collections").
   */
  private Queue<SnakeTailSpriteSelector> snakeTail = new Queue<SnakeTailSpriteSelector>();

  /*
   * Awake wird vor Start aufgerufen (das ist die erste Methode,
   * die in jedem Script aufgerufen wird.
   */
  public void Awake() {
    /*
     * Hier prüfen wir, ob vergessen wurde, notwendige
     * Attribute zu setzen und warnen den Benutzer im Falle
     * des Falles. Solche Prüfroutinen können einem später
     * sehr viel Zeit beim Suchen von Fehlern ersparen.
     */
    if (snakeHead == null) {
      Debug.LogError("Snake Head wurde nicht zugewiesen!");
      this.enabled = false; // diese Komponente abschalten
    }
    if (mover == null) {
      Debug.LogError("Mover wurde nicht zugewiesen!");
      this.enabled = false;
    }
    if (snakeTailPart == null) {
      Debug.LogError("Snake Tail Part wurde nicht zugewiesen!");
      this.enabled = false;
    }
    if (snakeTailParts == null) {
      Debug.LogError("Snake Tail Parts wurde nicht zugewiesen!");
      this.enabled = false;
    }
  }

  // Use this for initialization
  public void Start() {
    // setze beim Starten die aktuelle Länge
    // der Schlange auf die ursprüngliche Länge.
    currentTailLength = initialTailLength;

    // die "letzte Position" mit der Startposition initialisieren
    lastPosition = transform.position;
  }

  // In jedem Frame
  public void Update() {
    /*
     * Achtung: "Distance" is eine relativ teuere Operation,
     * allerdings ist ein Aufruf pro Frame harmlos. Eine naheliegende
     * Optimierung wäre hier, die Distanz jeweils nur auf der aktuellen Achse
     * zu berechnen.
     */
    float distance = Vector3.Distance(lastPosition, transform.position);

    /* 
     * Wechsel des "halben" Schwanz-Segments nach halber Distanz. Ohne
     * diese kleine "Optimierung" gibt es hässliche Effekte, da das
     * Schlangensegment beim Erzeugen etwas über den Kopf hinaus ragt.
     */
    if (lastPiece != null
        && lastPiece.IsShort
        && distance >= partCreationDistance * 0.5F) {
      lastPiece.ShowSnakeTailPart(SnakeTailPart.SnakeTail);
    }
    
    /*
     * Erzeugen neuer Schwanz-Segmente nach Distanz.
     */
    if (distance >= partCreationDistance) {
      CreateNewTailPart(currentAngle);
    }
  }

  /*
   * Diese Methode wird auch von SnakeHeadMover aus aufgerufen, und zwar
   * immer, wenn der Spieler einen Richtungswechsel durchführt. Dies
   * dient dazu, dass die "Ecken" sauber sind.
   */
  public void CreateNewTailPart(int newAngle) {
    bool hasTurned = currentAngle != newAngle;

    Vector3 currentPosition = snakeHead.position;
    // wir brauchen exakte Positionen, sonst gibt es Ränder!
    currentPosition.x = Mathf.RoundToInt(currentPosition.x);
    currentPosition.y = Mathf.RoundToInt(currentPosition.y);

    /* 
     * je nach Rundung kann es bei Drehungen passieren, 
     * dass wir noch auf der letzten Position sind
     */
    bool samePosition = ((int)lastPosition.x) == ((int)currentPosition.x)
      && ((int)lastPosition.y) == ((int)currentPosition.y);

    lastPosition = currentPosition;

    if (hasTurned) {
      /*
       * Wir erlauben Drehungen nur auf einem 1/1-Raster (sonst wird alles
       * erheblich komplizierter!). Hier wird der Schlangenkopf wieder
       * auf den Raster gesetzt.
       */
      snakeHead.position = lastPosition;
    }

    /*
     * Falls es ein letztes Teil gibt => Kollisionen jetzt auswerten
     * außer bei Kurven-Parts (die haben ihren eigenen, kleineren Collider)
     */
    if (!samePosition && lastPiece != null && !lastPiece.IsCurve) {
      Physics2D.IgnoreCollision(
        this.GetComponent<Collider2D>(), 
        lastPiece.GetComponent<Collider2D>(), 
        false);
    }

    SnakeTailSpriteSelector newTailPart = null;
    if (samePosition) {
      // Wiederverwendung des vorherigen Segments
      newTailPart = lastPiece;
    } else {
      /*
       * "(Transform)" macht aus dem allgemeinem Objekt, welches
       * Object.Instantiate(...) zurückliefert, welches tatsächlich
       * vom Typen "Transform" ist auch für den Compiler klar, dass
       * das hier ein "Transform" ist.
       */
      newTailPart = (SnakeTailSpriteSelector)Object.Instantiate(
          snakeTailPart,       // die "Vorlage" für unser neues Objekt
          lastPosition,        // die Position für das neue Objekt
          snakeHead.rotation); // die Rotation für das neue Objekt

      /*
       * counter wird mit ++ jeweils um eins hochgezählt, {0} ist
       * ein Platzhalter, der von string.Format(...) durch den ersten
       * Parameter nach dem String ersetzt wird; in dem Fall counter.
       */
      newTailPart.name = string.Format("Schlangenschwanz.{0}", counter++);

      // Räumen wir das Teil auf - wir hängen es unter "snakeTailParts", so
      // bleibt die Hierarchy im Editor übersichtlich.
      newTailPart.transform.parent = snakeTailParts;

      // Schlangensegment in die Schlange ;-)
      snakeTail.Enqueue(newTailPart);

      newTailPart.ShowSnakeTailPart(SnakeTailPart.SnakeTailShort);
    }

    if (hasTurned) {
      // Die "normalen Fälle":
      SnakeTailPart curve = (newAngle > currentAngle)
        ? curve = SnakeTailPart.SnakeCurveLeft
        : curve = SnakeTailPart.SnakeCurveRight;
      // Spezialfälle:
      if (newAngle == 0 && currentAngle == 270) {
        curve = SnakeTailPart.SnakeCurveLeft;
      } else if (newAngle == 270 && currentAngle == 0) {
        curve = SnakeTailPart.SnakeCurveRight;
      }
      if (samePosition) {
        lastPiece.ShowSnakeTailPart(curve);
        lastPiece.transform.rotation = Quaternion.Euler(0, 0, newAngle);
      } else {
        newTailPart.ShowSnakeTailPart(curve);
      }
    }


    // dieses Stück "merken" und Kollisionen deaktivieren, da sonst der Kopf
    // sofort mit dem neu erzeugten Stück kollidieren würde
    lastPiece = newTailPart;
    Physics2D.IgnoreCollision(
      this.GetComponent<Collider2D>(), 
      lastPiece.GetComponent<Collider2D>());

    /* 
     * Wenn die Schlange zu lange wird, nehmen wir das letzte
     * Stück einfach weg.
     */
    if (snakeTail.Count > currentTailLength) {
      SnakeTailSpriteSelector oldTailPart = snakeTail.Dequeue();
      Object.Destroy(oldTailPart.gameObject);
      ActivateSnakeTailEnd();
    }

    // Ganz am Anfang müssen wir die Schlangenschwanzspitze aktivieren
    if (snakeTail.Count == 1) {
      ActivateSnakeTailEnd();
    }

    currentAngle = newAngle;
  }

  private void ActivateSnakeTailEnd() {
    SnakeTailSpriteSelector nextLastTailPart = snakeTail.Peek();
    if (nextLastTailPart != null) {
      nextLastTailPart.ShowSnakeTailPart(SnakeTailPart.SnakeTailEnd);
    }
  }

  // Wird von SnakeCollisions aufgerufen, wenn die Schlange einen Apfel isst.
  public void EatOneApple() {
    currentTailLength += tailLengthPerApple;
  }

  public void DisableOnDeath() {
    // Schalte auf die Todes-Animation um (in diesem Fall nur ein Frame, also keine Bewegung)
    SnakeTailSpriteSelector nextLastTailPart = snakeTail.Peek();
    if (nextLastTailPart != null) {
      nextLastTailPart.DisableOnDeath();
    }

    // schalt mich aus
    enabled = false;
  }

  public void DisableOnWin() {
    // schalt mich aus
    enabled = false;
  }

}
