// TicTacThreeGame.cs
using System;
using System.ComponentModel;
using System.Collections.ObjectModel;
using System.Collections.Generic;

namespace DataDrivenUiSample {
  class TicTacThree : INotifyPropertyChanged {
    int dimension;
    int moveNumber;

    public TicTacThree(int dimension) {
      if( dimension < 3 || dimension > 9 ) { throw new Exception("Only accepting 3x3 thru 9x9 today"); }
      this.dimension = dimension;
      cells = new ObservableCollection<Cell>();
      for( int i = 0; i != dimension * dimension; ++i ) { cells.Add(new Cell(i)); }
      NewGame();
    }

    public void NewGame() {
      hasWinner = false;
      moveNumber = 0;
      SetCurrentPlayer("X");
      foreach( Cell cell in cells ) { cell.Move = null; }
    }

    public void Move(int cellNumber) {
      // Don't let multiple moves on the same cell change the player for a cell
      if( cells[cellNumber].Move != null ) { return; }

      // Record the move
      cells[cellNumber].Move = new PlayerMove(this.CurrentPlayer, ++moveNumber);

      // Only allow each player to have three moves on at once
      List<Cell> currentPlayerCells = new List<Cell>();
      foreach( Cell cell in cells ) {
        if( cell.Move != null && cell.Move.PlayerName == currentPlayer ) {
          currentPlayerCells.Add(cell);
        }
      }

      if( currentPlayerCells.Count == 4 ) {
        Cell earliestCell = currentPlayerCells[0];
        foreach( Cell cell in currentPlayerCells ) {
          if( cell.Move.MoveNumber < earliestCell.Move.MoveNumber ) {
            earliestCell = cell;
          }
        }
        earliestCell.Move = null;
      }

      // Check for winner
      if( HasWon(currentPlayer) ) {
        SetHasWinner(true);
        return;
      }

      // Switch player
      SetCurrentPlayer(currentPlayer == "X" ? "O" : "X");
    }

    bool HasWon(string player) {
      // Check every 3x3 box in the NxN square
      for( int rowOffset = 0; rowOffset != dimension - 2; ++rowOffset ) {
        for( int colOffset = 0; colOffset != dimension - 2; ++colOffset ) {

          int offset = rowOffset * dimension + colOffset;

          Cell[,] possibleWins = {
            // row
            { cells[0 + offset], cells[1 + offset], cells[2 + offset] },
            { cells[3 + offset], cells[4 + offset], cells[5 + offset] },
            { cells[6 + offset], cells[7 + offset], cells[8 + offset] },

            // column
            { cells[0 + offset], cells[3 + offset], cells[6 + offset] },
            { cells[1 + offset], cells[4 + offset], cells[7 + offset] },
            { cells[2 + offset], cells[5 + offset], cells[8 + offset] },

            // diagonal
            { cells[0 + offset], cells[4 + offset], cells[8 + offset] },
            { cells[2 + offset], cells[4 + offset], cells[6 + offset] },
          };

          for( int i = 0; i != possibleWins.GetLength(0); ++i ) {
            bool hasWon = true;
            for( int j = 0; j != possibleWins.GetLength(1); ++j ) {
              if( possibleWins[i, j].Move == null || possibleWins[i, j].Move.PlayerName != player ) {
                hasWon = false;
                break;
              }
            }

            if( hasWon ) {
              for( int j = 0; j != possibleWins.GetLength(1); ++j ) {
                possibleWins[i, j].Move.IsPartOfWin = true;
              }

              return true;
            }
          }
        }
      }

      return false;
    }

    ObservableCollection<Cell> cells;
    public IEnumerable<Cell> Cells {
      get { return cells; }
    }

    string currentPlayer;
    public string CurrentPlayer {
      get { return currentPlayer; }
    }

    protected void SetCurrentPlayer(string value) { currentPlayer = value; Notify("CurrentPlayer"); }

    bool hasWinner;
    public bool HasWinner {
      get { return hasWinner; }
    }

    protected void SetHasWinner(bool value) { hasWinner = value; Notify("HasWinner"); }

    // INotifyPropertyChanged Members
    public event PropertyChangedEventHandler PropertyChanged;
    void Notify(string propName) {
      if( PropertyChanged != null ) {
        PropertyChanged(this, new PropertyChangedEventArgs(propName));
      }
    }
  }

  class Cell : INotifyPropertyChanged {
    public Cell(int cellNumber) {
      this.cellNumber = cellNumber;
    }

    int cellNumber;
    public int CellNumber {
      get { return cellNumber; }
    }

    PlayerMove move;
    public PlayerMove Move {
      get { return move; }
      set { move = value; Notify("Move"); }
    }

    // INotifyPropertyChanged Members
    public event PropertyChangedEventHandler PropertyChanged;
    void Notify(string propName) {
      if( PropertyChanged != null ) {
        PropertyChanged(this, new PropertyChangedEventArgs(propName));
      }
    }
  }

  public class PlayerMove : INotifyPropertyChanged {
    string playerName;
    public string PlayerName {
      get { return playerName; }
      set {
        if( string.Compare(playerName, value) == 0 ) { return; }
        playerName = value;
        Notify("PlayerName");
      }
    }

    int moveNumber;
    public int MoveNumber {
      get { return moveNumber; }
      set {
        if( moveNumber == value ) { return; }
        moveNumber = value;
        Notify("MoveNumber");
      }
    }

    bool isPartOfWin = false;
    public bool IsPartOfWin {
      get { return isPartOfWin; }
      set {
        if( isPartOfWin == value ) { return; }
        isPartOfWin = value;
        Notify("IsPartOfWin");
      }
    }

    public PlayerMove(string playerName, int moveNumber) {
      this.playerName = playerName;
      this.moveNumber = moveNumber;
    }

    // INotifyPropertyChanged Members
    public event PropertyChangedEventHandler PropertyChanged;
    void Notify(string propName) {
      if( PropertyChanged != null ) {
        PropertyChanged(this, new PropertyChangedEventArgs(propName));
      }
    }
  }

}
