// M.D. Healy CSC 111
// Maze Class to hold the state of a maze

import java.io.*;
import java.util.Scanner;

/*
 *  The constructor for this class expects to be
 *  given a string representing the name of a maze
 *  file.  It parses this file and creates a maze
 *  object.  A Creature can interact with the maze
 *  through various getter methods that return the
 *  dimensions, entry coordinates, exit coordinates,
 *  and state of any cell.  The Creature can also use
 *  a setter method to set the state of a cell as it
 *  explores the maze.  NOTE THAT COORDINATES ARE
 *  ZERO-BASED.  Top row is row zero.  Left col is
 *  col zero.
 *
 *  File format is:
 *
 20     7  width and height of the maze in squares
  0    18  row and column of entrance
  6    12  row and column of exit
 xxxxxxxxxxxxxxxxxx x   
 xx xx  xxxxxx      x
 xx     xx    xxxxxxx
 xxx   xxx xxxxx  xxx
 xxxxx  xx xx   xxxxx
 xxxxxx        xxxxxx
 xxxxxxxxxxxx xxxxxxx
*/

public class Maze
{

      public static void main(String[] args)
        {
           // Test method: tries to create and print a maze
           if (args.length == 0)
             {
                System.out.println("Need a filename!");
             }
          else
             {
               Maze myMaze = new Maze(args[0]);

               System.out.println("\nParsing Maze file " + args[0]);

               System.out.println(
                                    "Num Rows: " + myMaze.getNumRows() +
                                    " Num Cols: " + myMaze.getNumCols()
                                 );
               // display the maze
               System.out.println(myMaze);

             }
        }

  // Define an enumerated type for a maze square
  public enum State {
                        CLEAR , WALL , VISITED ,
                        PATH , CREATURE , ENTRANCE , EXIT
                    }
  // Possible states for a square in the maze
  //
  //   CLEAR       = not in a wall and not been visited by creature
  //   WALL        = part of a wall
  //   PATH        = on the path to a solution
  //   VISITED     = creature has been here but it led to impasse
  //   CREATURE    = creature is here right now
  //   ENTRANCE    = cell is the entrance to the maze
  //                 AND CREATURE HAS NOT BEEN THERE YET
  //   EXIT        = cell is the exit from the maze
  //                 AND CREATURE HAS NOT BEEN THERE YET

  private int numRows , numCols;
  private int moveCount = 0;

  private State[][] Cell;
  // Two-dimensional array to hold the cells of the maze.
  // We use the enumerated type 

  private int entranceX;
  private int entranceY;

  private int creatureX;
  private int creatureY;

  private int exitX;
  private int exitY;

  // Public constructor for a Maze
  public Maze (String fileName)
    {
       // Attempt to open and read the given input file
       BufferedReader inFile;
       String inputLine;
       try
        {
          int linesRead = 0;
          inFile = new BufferedReader(new FileReader(fileName));
          while ((inputLine = inFile.readLine()) != null)
            {
               linesRead++;
               ParseInputLine(inputLine , linesRead);
            }
        } // end try
      catch (IOException e)
        {
          System.out.println("IO Exception " + e);
          System.exit(1);  // like die() in Perl...
        }

       // Mark the entrance and exit of the maze...
       Cell[entranceX][entranceY] = State.ENTRANCE;
       Cell[exitX][exitY] = State.EXIT;

    } // end constructor

  private void ParseInputLine (String line , int nLines)
    {
      if ((nLines > 3) && (nLines <= numRows + 3))
        {
           // We are looking at the actual Maze data

           for (int i = 0 ; i < numCols ; i++)
             {
                // fill a row of our maze

                if (i >= line.length())
                  {
                      System.out.println("Input error: cannot parse line " +
                                   line +
                                   " which should have at least" +
                                    numCols + " characters");
                      System.exit(1);  // like die() in Perl...
                  }

                // Note the concatenation of a zero-length String, which
                // is merely a simple way of coercing line.charAt into
                // a String so that it can be compared with a String literal
                String myChar = "" + line.charAt(i);

                if ( (myChar.equals("X") || (myChar.equals("x")) ))
                  {
                    // We are looking at a WALL
                     Cell[i][nLines-4] = State.WALL;
                  }

                else if (myChar.equals(" "))
                  {
                    // We are looking at a CLEAR space
                     Cell[i][nLines-4] = State.CLEAR;
                  }

                else
                  {
                    // We are looking at an ILLEGAL state
                      System.out.println("Input line " +
                                         line +
                                         " has an ILLEGAL character"
                                         + line.charAt(i) +
                                         " at position " + i);
                      System.exit(1);  // like die() in Perl...
                  }
             } // end for i loop

        } // end if looking at maze data
      else if (nLines <= 3)
        {
           // we are looking at a line with numerical coordinates
           int n1 = 0;
           int n2 = 0;
           // While I do not think I need to initialize these two ints,
           // the compiler complains with a warning about
           // "...might not have been initialized" unless I do

           Scanner myScanner = new Scanner(line);

           if (myScanner.hasNextInt())
             {
                n1 = myScanner.nextInt();
             }
           else
             {
                System.out.println("Input error: cannot parse line " +
                                   line +
                                   " which should start with two integers");
                System.exit(1);  // like die() in Perl...
             }

           if (myScanner.hasNextInt())
             {
                n2 = myScanner.nextInt();
             }
           else
             {
                System.out.println("Input error: cannot parse line " +
                                   line +
                                   " which should start with two integers");
                System.exit(1);  // like die() in Perl...
             }

           // Ok, if we get here, then we have found two integers
           // and are ready to make use of them...

           if (nLines == 1)
             {
               // we're in the first line, so now we know the
               // overall dimensions of our maze...

               // NOTE the specified file format gives WIDTH then HEIGHT

               numCols = n1;
               numRows = n2;

               Cell = new State[n1][n2];

             } // end processing of FIRST line

           if (nLines == 2)
             {
               // we're in the second line, so now we know the
               // coordinates of the entrance to our maze

               // Note that while first line has WIDTH first,
               // this line has ROW first!

               entranceY = n1;
               entranceX = n2;

               // Default creature starting position is
               // also the entrance:

               creatureY = n1;
               creatureX = n2;

             } // end processing of SECOND line

           if (nLines == 3)
             {
               // Note that while first line has WIDTH first,
               // this line has ROW first!

               // we're in the third line, so now we know the
               // coordinates of the exit from our maze
               exitY = n1;
               exitX = n2;
             } // end processing of THIRD line

         }  // end processing of FIRST THREE lines (with integers)

    } // End parsing of lines...


  // accessor methods

  public int getNumRows()
    {
      return numRows;
    }

  public int getNumCols()
    {
      return numCols;
    }

  public int getEntranceX ()
    {
      return entranceX;
    }

  public int getEntranceY ()
    {
      return entranceY;
    }

  public int getExitX ()
    {
      return exitX;
    }

  public int getExitY ()
    {
      return exitY;
    }

  public State getState(int x , int y)
    {
       return Cell[x][y];
    }

  public int getCreatureX () {
     return creatureX;
    }

  public int getCreatureY () {
     return creatureY;
    }

  public int getMoveCount () {
     return moveCount;
    }

  public void setState(int x , int y, State s)
    {
       Cell[x][y] = s;
       moveCount++;
    }

  public void setCreatureX (int x) {
     creatureX = x;
    }

  public void setCreatureY (int y) {
     creatureY = y;
    }


  public String toString()
    {
       // Stringify myself...

       StringBuffer mySelfStringified =
          new StringBuffer("");

       for (int i = 0 ; i < this.getNumRows(); i++)
         {
           for (int j = 0 ; j < this.getNumCols(); j++)
             {
               // Spit out a cell
               if (Cell[j][i] == State.WALL)
                 {
                    mySelfStringified.append("x");
                 }
              else if (Cell[j][i] == State.CLEAR)
                 {
                    mySelfStringified.append(" ");
                 }
              else if (Cell[j][i] == State.VISITED)
                 {
                    mySelfStringified.append(".");
                 }
              else if (Cell[j][i] == State.PATH)
                 {
                    mySelfStringified.append("^");
                 }
              else if (Cell[j][i] == State.CREATURE)
                 {
                    mySelfStringified.append("o");
                 }
              else if (Cell[j][i] == State.ENTRANCE)
                 {
                    mySelfStringified.append("+");
                 }
              else if (Cell[j][i] == State.EXIT)
                 {
                    mySelfStringified.append("-");
                 }
             } // end j for loop
           // row number then a newline
           mySelfStringified.append("  " + i + "\n");
         } // end i for loop

       // Add column numbering to bottom
       // first the tens digit...
       for (int j = 0 ; j < this.getNumCols(); j++)
         {
            mySelfStringified.append(j/10);
         }

       mySelfStringified.append("\n");

       // now the ones digit...
       for (int j = 0 ; j < this.getNumCols(); j++)
         {
            mySelfStringified.append(j%10);
         }

       return mySelfStringified.toString();


    } // end toString method

} // end Maze class