//  Datei Puzzle.swift


import UIKit

// Klasse zur Speicherung des Puzzlespiels
class Puzzle {
  var size: Int          // Anzahl der Spalten/Zeilen
  var tiles: [[Tile?]]   // Array mit Puzzleteilen
  
  init(size: Int, img: UIImage, vc: ViewController) {
    // Eigenschaften initialisieren
    self.size = size
    tiles = [[Tile?]](repeating: [Tile?](
      repeating: nil, count: size), count: size)
    
    // evt. alte Puzzleteile löschen
    vc.puzzleView.subviews.forEach() { $0.removeFromSuperview() }
    
    for col in 0..<size {
      for row in 0..<size {
        // Ecke rechts unten bleibt frei
        if row == size-1 && col == row { break }
        
        // Puzzlestück einrichten
        let t = Tile()
        t.row = row
        t.col = col
        t.image = img.crop(col: col, row: row, cnt: size)
        t.layer.borderColor = UIColor.lightGray.cgColor
        
        // eigenes GestureRecognizer-Objekt für
        // jedes Puzzlestück erforderlich!
        let gesture = UIPanGestureRecognizer(
          target: vc,
          action: #selector(ViewController.panTile(recognizer:)))
        t.isUserInteractionEnabled = true // damit Recognizer fn.
        t.addGestureRecognizer(gesture)
        
        // Tiles in die PuzzleView einfügen
        vc.puzzleView.addSubview(t)
        tiles[col][row] = t
      }
    }
  }
  
  // passt Position und Größe aller Puzzlestücke an
  func setTilesFrame(in view: UIView) {
    for col in 0..<size {
      for row in 0..<size {
        tiles[col][row]?.frame = getRect(col: col, row: row, container: view)
      }
    }
  }
  
  // ermittelt CGRect für Zielposition eines Puzzlesteins innerhalb
  // eines quadratischen Containers
  func getRect(col: Int, row: Int, container: UIView) -> CGRect {
    let side = container.bounds.width / CGFloat(size)
    return CGRect(x: CGFloat(col) * side,
                  y: CGFloat(row) * side,
                  width: side,
                  height: side)
  }
  

  // findet heraus, an welcher Position im puzzle-Array
  // ein bestimmtes Teil ist
  func getPosition(of t: Tile) -> (Int, Int)? {
    for row in 0..<size {
      for col in 0..<size {
        if tiles[col][row] == t {
          return (col, row)
        }
      }
    }
    // nicht gefunden
    return nil
  }
  
  // versucht ein Puzzlestück in eine bestimmte Richtung
  // zu verschieben; schiebt alle davorliegenden Steine mit.
  // duration gibt die Animations-Dauer an, completion eine
  // optionale Funktion, die nach der Fertigstellung der
  // Animation aufgerufen wird. Gibt true/false zurück, je nachdem,
  // ob der Zug möglich war oder nicht
  func moveTile(col: Int, row: Int, dir: Direction,
                container: UIView,
                duration: Double,
                completion: ((Bool) -> Void)? = nil) -> Bool
  {
    // da ist nichts zum Verschieben
    if tiles[col][row] == nil { return false }
    
    // welche Teile können verschoben werden
    // (jeweils als Koordinatenpaar speichern)
    var toMove = [(row: Int, col: Int)]()
    toMove.append((row: row, col: col))
    
    switch_construct:
    switch dir {
    case .right:
      for x in col+1..<size {
        if tiles[x][row]==nil { break switch_construct }
        toMove.append((col:x, row:row))
      }
      return false  // keinen freien Platz gefunden
      
    case .left:
      var x = col-1
      while(x>=0) {
        if tiles[x][row] == nil { break switch_construct }
        toMove.append((col:x, row:row))
        x-=1
      }
      return false  // keinen freien Platz gefunden
      
    case .down:
      for y in row+1..<size {
        if tiles[col][y] == nil { break switch_construct }
        toMove.append((col:col, row:y))
      }
      return false  // keinen freien Platz gefunden
      
    case .up:
      var y = row-1
      while(y>=0) {
        if tiles[col][y] == nil { break switch_construct }
        toMove.append((col: col, row: y))
        y-=1
      }
      return false  // keinen freien Platz gefunden
    }
    
    // mögliche Verbesserung: für alle Tiles vorübergehend
    // isUserInteractionEnabled = false stellen, damit nicht
    // mehrere Verschiebeoperationen parallel ausgeführt werden 
    // können
    
    // in umgekehrter Reihenfolge verarbeiten, damit
    // puzzle-Änderungen nichts überschreiben
    for tile in toMove.reversed() {
      let (col, row) = (tile.col, tile.row)
      let newcol:Int, newrow: Int
      switch dir {
      case .right:
        newcol = col+1
        newrow = row
      case .left:
        newcol = col-1
        newrow = row
      case .down:
        newcol = col
        newrow = row+1
      case .up:
        newcol = col
        newrow = row-1
      }
      // Teile verschieben; beim letzten Schritt complete-Funktion aufrufen
      if tile == toMove.first! {
        UIView.animate(
          withDuration: duration,
          animations: { // Closure
            self.tiles[col][row]!.frame =
              self.getRect(col: newcol, row: newrow, container: container) },
          completion: completion)
      } else {
        UIView.animate(withDuration: duration) {  // Closure
          self.tiles[col][row]!.frame =
            self.getRect(col: newcol, row: newrow, container: container)
        }
      }
      // Bewegung auch im puzzle-Array nachvollziehen
      tiles[newcol][newrow] = tiles[col][row]
    }
    
    // Startfeld der Bewegung ist jetzt leer
    tiles[col][row]=nil
    setBorder()
    return true
  }
  
  // wenn alle Puzzleteile richtig: gar kein Rand,
  // einzelne Teile, die richtig sind: dünner Rand
  // Teile, die nicht passen: dicker Rand
  func setBorder() {
    var puzzleOK = true
    for row in 0..<size {
      for col in 0..<size {
        if let t = tiles[col][row] {
          if t.row != row || t.col != col {
            puzzleOK = false
            t.layer.borderWidth = 2
          } else {
            t.layer.borderWidth = 0.5
          }
          
        }
      }
    }
    
    if puzzleOK {
      for row in 0..<size {
        for col in 0..<size {
          tiles[col][row]?.layer.borderWidth = 0
        }
      }
    }
  }

  

}
