package OOPLS;

import java.awt.Color;
import java.awt.Graphics;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.util.LinkedList;
import javax.swing.JPanel;
import java.awt.Rectangle;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.Iterator;
import javax.swing.JOptionPane;

public class MazePanel extends JPanel {
    final static int DEFAULT_ROWS = 30, DEFAULT_COLUMNS = 40;
    final static Color PATH_COLOR = Color.MAGENTA,
            FINISHED_PATH_COLOR = new Color(0f, 0f, 0.8f);
    int cellWidth, cellHeight;
    Rectangle rect;
    Maze maze;
    LinkedList<Indices2D> solution = 
        new LinkedList<Indices2D>();
    boolean spaceDown = false;
    public MazePanel() {
        maze = new Maze(DEFAULT_ROWS,DEFAULT_COLUMNS);        
        solution.add(maze.getStart());
        setFocusable(true);
        addKeyListener(new KeyAdapter() {
            public void keyPressed(KeyEvent e) {
                if (maze.getFinish().equals(solution.getLast())) 
                    return;
                Indices2D current = solution.removeLast();
                Indices2D last = null;
                if (!solution.isEmpty()) 
                    last = solution.getLast();
                solution.add(current);
                switch(e.getKeyCode()) {
                    case KeyEvent.VK_UP: 
                        goNorth(current,last); break;
                    case KeyEvent.VK_RIGHT: 
                        goEast(current,last); break;
                    case KeyEvent.VK_DOWN: 
                        goSouth(current,last); break;
                    case KeyEvent.VK_LEFT: 
                        goWest(current,last); break;
                    case KeyEvent.VK_BACK_SPACE: 
                        if (solution.size() > 1) 
                            solution.removeLast(); break;
                    case KeyEvent.VK_SPACE:
                        spaceDown = true;
                }
                repaint();
            }
            public void keyReleased(KeyEvent e) {
                if (spaceDown) {
                    spaceDown = false;
                    repaint();
                }
            }
        });
    }
    void goNorth(Indices2D current, Indices2D last) {
        if (current.i > 0 && 
                !maze.hasWall(current.i,current.j,Maze.NORTH))
            if (last == null || last.i != current.i - 1)
                solution.add(new Indices2D(
                    current.i - 1,current.j));
            else solution.removeLast();
    }
    void goEast(Indices2D current, Indices2D last) {
        if (current.j < maze.getNumColumns()-1 && 
                !maze.hasWall(current.i,current.j,Maze.EAST))
            if (last == null || last.j != current.j + 1)
                solution.add(new Indices2D(
                    current.i,current.j + 1));
            else solution.removeLast();
    }
    void goSouth(Indices2D current, Indices2D last) {
        if (current.i < maze.getNumRows() - 1 && 
            !maze.hasWall(current.i, current.j, Maze.SOUTH))
            if (last == null || last.i != current.i + 1) 
                solution.add(new Indices2D(
                    current.i + 1,current.j));
            else solution.removeLast();
    }
    void goWest(Indices2D current, Indices2D last) {
        if (current.j > 0 && 
            !maze.hasWall(current.i, current.j, Maze.WEST))
            if (last == null || last.j != current.j - 1)
                solution.add(new Indices2D(
                    current.i,current.j - 1));
            else solution.removeLast();
    }
    public void newMaze() {
        newMaze(maze.getNumRows(),maze.getNumColumns());
    }
    public void newMaze(int m, int n) {
        maze = new Maze(m,n);
        eraseSolution();
        repaint();
    }
    public void eraseSolution() {
        solution.clear();
        solution.add(maze.getStart());
    }
    public Maze getMaze() { return maze; }
    public void paint(Graphics g) {
        super.paint(g);
        rect = getBounds();
        cellWidth = (int)(rect.getWidth()/maze.getNumColumns());
        cellHeight = (int)(rect.getHeight()/maze.getNumRows());
        if (solution.getLast().equals(maze.getFinish())) {
            g.setColor(FINISHED_PATH_COLOR);
        } 
        else {
            g.setColor(PATH_COLOR);
        }
        Iterator<Indices2D> i = solution.iterator();
        Indices2D last = i.next();
        while (i.hasNext()) {
            Indices2D next = i.next();
            int i1=last.i, j1=last.j, i2=next.i, j2=next.j;
            drawThickLine(g,
                j1*cellWidth+cellWidth/2, 
                i1*cellHeight+cellHeight/2,
                j2*cellWidth+cellWidth/2, 
                i2*cellHeight+cellHeight/2);
            last = next;
        }
        g.setColor(Color.BLACK);
        maze.draw(g, rect);
        if (spaceDown) {
            int radiusOfCircle = 
                Math.max(rect.width/20, 2*cellWidth);
            g.setColor(Color.GREEN);
            g.drawOval(
              maze.getStart().j*cellWidth + 
                  cellWidth/2 - radiusOfCircle, 
              maze.getStart().i*cellHeight + 
                  cellHeight/2 - radiusOfCircle, 
              2*radiusOfCircle,2*radiusOfCircle);
            g.setColor(Color.RED);
            g.drawOval(
              maze.getFinish().j*cellWidth + 
                  cellWidth/2 - radiusOfCircle, 
              maze.getFinish().i*cellHeight + 
                  cellHeight/2 - radiusOfCircle,
              2*radiusOfCircle,2*radiusOfCircle);
        }    
    }
    void drawThickLine(Graphics g, int x1, int y1, 
                                   int x2, int y2) {
        if (x1 == x2) {
            int width = cellWidth/2;
            int height = y2>y1?(y2-y1+1):(y1-y2+1);
            g.fillOval(x1-width/2,Math.min(y1,y2),width,height);
        }
        else {
            int height = cellHeight/2;
            int width = x2>x1?(x2-x1+1):(x1-x2+1);
            g.fillOval(Math.min(x1,x2),y1-height/2,width,height);
        }
    }
    public boolean saveMaze(String filename) {
        try {
            ObjectOutputStream binaryOut =
                    new ObjectOutputStream(
                    new BufferedOutputStream(
                    new FileOutputStream(filename)));
            binaryOut.writeObject(maze);
            binaryOut.writeObject(solution);
            binaryOut.flush();
            binaryOut.close();
            return true;
        } catch (Exception exc) {
            JOptionPane.showMessageDialog(this,
                "Could not save to '"+filename+"'");
            return false;
        }
    }

    public boolean openMaze(String filename) {
        Maze oldMaze = maze;
        LinkedList<Indices2D> oldSolution = solution;
        try {
            ObjectInputStream binaryIn =
                    new ObjectInputStream(
                    new BufferedInputStream(
                    new FileInputStream(filename)));
            maze = (Maze)binaryIn.readObject();
            solution = 
                (LinkedList<Indices2D>)binaryIn.readObject();
            repaint();
            return true;
        } catch(Exception exc) {
            JOptionPane.showMessageDialog(this,
                "Could not open '"+filename+"'");
            maze = oldMaze;
            solution = oldSolution;
            return false;
        }
    }
} // End of class MazePanel
