package org.artisanlogiciel.games;

import java.awt.BorderLayout;
import java.awt.image.BufferedImage;
import java.awt.Color;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.Graphics;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.awt.event.InputEvent;
import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionListener;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.LinkedList;
import java.util.Locale;
import java.util.Scanner;
import java.util.ResourceBundle;

import javax.imageio.ImageIO;

import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.BoxLayout;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JCheckBox;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JSlider;
import javax.swing.JTextField;
import javax.swing.KeyStroke;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;

import org.artisanlogiciel.games.stl.Wall3d;
import org.artisanlogiciel.util.UTF8Control;

import org.artisanlogiciel.graphics.Drawing;
import org.artisanlogiciel.graphics.DrawingLine;
import org.artisanlogiciel.graphics.SvgWriter;

/**
 * Display is Main JFrame for this tool
 **/
public class Display extends JFrame
{
    // to please eclipse, not supposed to be serialized
    private static final long serialVersionUID = 8500214871372184418L;

    final static ResourceBundle labels = ResourceBundle.getBundle("LabelsBundle", Locale.getDefault(), Display.class.getClassLoader(), new UTF8Control());
    
    MazeComponent maze;
    MazeControler controler;
    LabyModel model;
    JPanel controlPanel;
    boolean autoSize;

    MazeParams params = null;

    Drawing drawing = new Drawing();

    Display(LabyModel model,int W, int H, MazeParams params)
    {
        super(labels.getString("title"));
	if (params != null)
	    {
		// fixedParams = new MazeParamsFixed(params.getSaveDir(),params.getWidth(),params.getHeight(),params.getMaxDepth());
		this.params = params;
	    }
	this.model = model;
        maze = createMazeComponent(model,W,H);
	
        Container con = this.getContentPane();
	JScrollPane scrollableMaze = new JScrollPane(maze);
	scrollableMaze.addMouseMotionListener(maze);
        con.add(scrollableMaze, BorderLayout.CENTER);
        controler = new MazeControler(params);
        con.add(controler.getMoveControl(), BorderLayout.NORTH);
	con.add(controler.getGenerationControl(), BorderLayout.SOUTH);

	addComponentListener(new ComponentAdapter() {
		@Override
		public void componentResized(ComponentEvent e) {
		    if ( autoSize )
			{
			    maze.getAutoSize();
			}
		}
	    });

	model.setMazeListener(maze);
	setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
	setBounds(W, H, W, H);
	setVisible(true);
    }

    private static MazeComponent createMazeComponent(LabyModel model, int W, int H)
    {
	MazeCellParameters cp = new MazeCellParameters(model.getWidth(),model.getHeight(), W, H, 3, 3);
	MazeComponent comp = new MazeComponent(model, cp);		
	return comp;
    }

    void recreateModel()
    {
	// recreate labyrinth
	if (params != null)
	    {
		params = controler.getSettings().getParams();
		// keep current model...
		// model = new LabyModel(params, new java.util.Random());
		maze.resetWallsProvider(model);
		model.setMazeListener(maze);

		model.reset();
		
		// we are in GUI event thread, need to release it and do it outside.
		new Thread() {
		    public void run() {			
			model.generateWithEntry(0, 0);
			model.addEntryOrExit(-1, 0);
			model.addEntryOrExit(params.getWidth(), params.getHeight() - 1);
		    }
		}.start();
	    }
    }

    void resetModel()
    {
	// recreate labyrinth
	if (params != null)
	    {
		params = controler.getSettings().getParams();
		model = new LabyModel(params, new java.util.Random());
		maze.resetWallsProvider(model);
		model.setMazeListener(maze);

		
		// we are in GUI event thread, need to release it and do it outside.
		new Thread() {
		    public void run() {
			maze.changed(null,null,model);
		    }
		}.start();


	    }
    }

    void resolve()
    {
	model.reset();
        model.resolve(model.getWidth() - 1, model.getHeight() - 1, maze);
    }

    void goNorth()
    {
        maze.goNorth();
    }

    void goSouth()
    {
        maze.goSouth();
    }

    void goEast()
    {
        maze.goEast();
    }

    void goWest()
    {
        maze.goWest();
    }

    void setWallSize(int size)
    {
        maze.setWallSize(size);
    }

    void setAutoSize(boolean autoSize)
    {
	this.autoSize = autoSize;
	if ( autoSize )
	    {
		maze.getAutoSize();
	    }
    }

    void addWallInDrawing(int pX, int pY, Drawing d)
    {
	short walls = model.getWalls(pX,pY);
	short wdrawn = 0;
	// todo
	int w = 2;	
	int h = 2;
	int ox=0;
	int oy=0;
	
	int x = ox + (int) (pX * w);
	int y = oy + (int) (pY * h);
	DrawingLine dl = new DrawingLine();
	// order matters since all points are linked
	if ( (pY == 0) && LabyModel.isFlagSet(walls,Brick.UP) )
	    {
		dl.addPoint(new Point(x, y));
		dl.addPoint(new Point(x + (int) w, y));
		wdrawn |= Brick.UP;
	    }
	if (LabyModel.isFlagSet(walls,Brick.RIGHT))
	    {
		if ( ! LabyModel.isFlagSet(wdrawn,Brick.UP) )
		    {
			dl.addPoint(new Point(x + (int) w, y));
		    }
		dl.addPoint(new Point(x + (int) w, y + (int) h));
		wdrawn |= Brick.RIGHT;
	    }
	if (LabyModel.isFlagSet(walls,Brick.DOWN))
	    {
		if ( ! LabyModel.isFlagSet(wdrawn,Brick.RIGHT) )
		    {
			if (wdrawn != 0)
			    {
				d.addLine(dl);
				dl = new DrawingLine();
			    }
			dl.addPoint(new Point(x + (int) w, y + (int) h));
		    }
		dl.addPoint(new Point(x, y + (int) h));
		wdrawn |= Brick.DOWN;
	    }
	if ( (pX == 0 ) && LabyModel.isFlagSet(walls,Brick.LEFT))
	    {
		if ( ! LabyModel.isFlagSet(wdrawn,Brick.DOWN) )
		    {
			if (wdrawn != 0)
			    {
				d.addLine(dl);
				dl = new DrawingLine();
			    }
			dl.addPoint(new Point(x, y + (int) h));
		    }
		dl.addPoint(new Point(x, y));
		wdrawn |= Brick.LEFT;
	    }
	if (wdrawn != 0)
	    {
		d.addLine(dl);
	    }
    }

    Drawing createDrawing()
    {
	Drawing d = new Drawing();
	{
	    short walls;
	    
    	    // draw all walls within clip bounds horiz first then lines
            for (int y = 0; y < model.getHeight(); y++)
            {
                for (int x = 0; x < model.getWidth(); x ++)
                {
		    addWallInDrawing(x,y,d);
                }
            }
	}
	return d;
    }

    // to allow to log / write somewher into screen...
    void writeSentence(String pSentence)
    {
	// TODO
	System.out.println( pSentence);
    }

    void writeError(String pError)
    {
	System.err.println(pError);
    }

    void saveImc()
    {
	Drawing d = createDrawing();

	if ( d != null )
	    {
		File outfile = new File(params.getSaveDir(), params.getName() + ".imc");
		writeSentence("Saving to " + outfile + " ...");
		try
		    {
			DataOutputStream out = new DataOutputStream(new FileOutputStream(outfile));
			d.saveLinesKompressed(out);
			out.flush();
			out.close();
			writeSentence("... Done.");
		    }
		catch (IOException io)
		    {
			io.printStackTrace(System.err);
		    }
	    }
    }

    void saveSvg()
    {
	Drawing d = createDrawing();

	if ( d != null )
	    {
		File outfile = new File(params.getSaveDir(), params.getName() + ".svg");
		writeSentence("Saving to " + outfile + " ...");
		try
		    {
			DataOutputStream out = new DataOutputStream(new FileOutputStream(outfile));
			SvgWriter writer = new SvgWriter(d.getInternLines());
			writer.writeTo(out);
			out.flush();
			out.close();
			writeSentence("... Done.");
		    }
		catch (IOException io)
		    {
			io.printStackTrace(System.err);
		    }
	    }
	else
	    {
		writeError("drawing creation failed");
	    }
    }


    
    void savePng()
    {
	File file = new File("snapshot.png");
	// BufferedImage bi = new BufferedImage(this.getSize().width, this.getSize().height, BufferedImage.TYPE_INT_ARGB);
	BufferedImage bi = new BufferedImage(maze.getSize().width, maze.getSize().height, BufferedImage.TYPE_INT_ARGB);
        Graphics g = bi.createGraphics();
        // this.paint(g);
	maze.paint(g);
        g.dispose();
        try{
            ImageIO.write(bi,"png",file);
        }catch (Exception e) {
            e.printStackTrace();
        }
    }

    private class MazeSettings extends JPanel
    {
	MazeParams params;

	JTextField textWidth = null;
	JTextField textHeight = null;
	JTextField textDepth = null;
	
	// TODO set width and height and depth of maze with gui
	public MazeSettings(MazeParams params)
	{
	    super();
	    this.params = params;
	    createSettingsGui();
	}

	void createSettingsGui()
	{
	    if ( params != null )
		{
		    JButton buttonCreate = new JButton(labels.getString("create"));
		    buttonCreate.addActionListener(new ActionListener()
			{
			    public void actionPerformed(ActionEvent evt)
			    {
				recreateModel();
			    }		    
			});		    
		    add(buttonCreate);

		    JButton buttonReset = new JButton("reset");//labels.getString("reset"));
		    buttonReset.addActionListener(new ActionListener()
			{
			    public void actionPerformed(ActionEvent evt)
			    {
				resetModel();
			    }		    
			});
		    add(buttonReset);

		}
	    if ( params != null )
		{
		    JLabel widthLabel = new JLabel(labels.getString("width"));
		    textWidth = new JTextField("0" + params.getWidth());
		    add(widthLabel);
		    add(textWidth);
		    JLabel heightLabel = new JLabel(labels.getString("height"));
		    textHeight = new JTextField("0" + params.getHeight());
		    add(heightLabel);
		    add(textHeight);
		    JLabel depthLabel = new JLabel(labels.getString("depth"));
		    textDepth = new JTextField("0" + params.getMaxDepth());
		    add(depthLabel);
		    add(textDepth);
		}
	}

	public MazeParams getParams()
	{
	    return new MazeParamsFixed(params.getSaveDir(),
				       Integer.parseInt(textWidth.getText()),
				       Integer.parseInt(textHeight.getText()),
				       Integer.parseInt(textDepth.getText()));
	}

    }

    private static class MazeParamsFixed implements MazeParams
    {
	int width;
	int height;
	int maxdepth;
	File labdir;
	String name;

	public MazeParamsFixed()
	{	    
	}

	public MazeParamsFixed(MazeParams params)
	{
	    setParams(params.getSaveDir(),params.getWidth(),params.getHeight(),params.getMaxDepth());
	}

	public void setParams(File saveDir, int W, int H, int MD)
	{
	    labdir = saveDir;
	    width=W;
	    height=H;
	    maxdepth=MD;
	}

	public MazeParamsFixed(File saveDir, int W, int H, int MD)
	{
	    name = null;
	    setParams(saveDir,W,H,MD);
	}

	public int getWidth()
	{
	    return width;
	}

	public int getHeight()
	{
	    return height;
	}

	public int getMaxDepth()
	{
	    return maxdepth;
	}

	public void setName(String n)
	{
	    name = n;
	}

	public String getName()
	{
	    if (name == null)
		{
		    name = "lab" + width + "x" + height;
		}
	    return name;
	}

	public File getSaveDir()
	{
	    return labdir;
	}
    }


    private class MazeControler extends JPanel
    {
        /**
         * 
         */
        private static final long serialVersionUID = 1L;

	private MazeSettings settings = null;

	private JPanel resizecontrol = null;

	private void setMazeName(String pName)
	{
	    MazeParamsFixed p = (MazeParamsFixed) params;
	    p.setName(pName);
	}

	private JPanel createSavingBar()
	{
	    final JTextField saveName = new JTextField("newlaby ");
	    final JButton savePngButton = new JButton(labels.getString("save") +" png");
	    Action savePngAction = new AbstractAction() {
                public void actionPerformed(ActionEvent evt)
                {
                    //
                    System.out.println("save png");
		    setMazeName(saveName.getText());
                    savePng();
                }
            };
	    savePngButton.addActionListener(savePngAction);
	    final JButton saveSvgButton = new JButton(labels.getString("save") +" svg");
	    Action saveSvgAction = new AbstractAction() {
                public void actionPerformed(ActionEvent evt)
                {
                    writeSentence("save png");
		    setMazeName(saveName.getText());
                    saveSvg();
                }
            };
	    saveSvgButton.addActionListener(saveSvgAction);
	    final JButton saveButton = new JButton(labels.getString("save") +" raw");
	    Action saveAction = new AbstractAction() {
                public void actionPerformed(ActionEvent evt)
                {
                    //
                    System.out.println("save");
		    setMazeName(saveName.getText());
		    MazeParamsFixed p = (MazeParamsFixed) params;
                    save(p,model);
                }
            };
	    saveButton.addActionListener(saveAction);
	    final JButton saveImcButton = new JButton(labels.getString("save") +" imc");
	    Action saveImcAction = new AbstractAction() {
                public void actionPerformed(ActionEvent evt)
                {
                    //		    
                    System.out.println("save imc");
		    setMazeName(saveName.getText());
		    saveImc();
                }
            };
	    saveImcButton.addActionListener(saveImcAction);

	    JPanel savePanel = new JPanel(new FlowLayout());
	    savePanel.add(saveName);
	    savePanel.add(saveSvgButton);
	    savePanel.add(savePngButton);
	    savePanel.add(saveButton);
	    savePanel.add(saveImcButton);

	    return savePanel;
		
	}

	private JPanel createResolveQuitBar()
	{
	    JPanel resolveQuitBar = new JPanel(new FlowLayout());
	    JButton resolveButton = new JButton(labels.getString("resolve"));
	    resolveButton.addActionListener(new ActionListener()
            {
                public void actionPerformed(ActionEvent evt)
                {
                    //
                    System.out.println("Resolving");
                    resolve();
                }
            });
	    
	    resolveQuitBar.add(resolveButton);

	    	    final JButton quitButton = new JButton(labels.getString("quit"));
	    Action quitAction = new AbstractAction() {
                public void actionPerformed(ActionEvent evt)
                {
                    //
                    System.out.println("quit");
		    System.exit(0);
                }
            };
	    quitButton.addActionListener(quitAction);

	    resolveQuitBar.add(quitButton);

	    return resolveQuitBar;

	}
	
        public MazeControler(MazeParams params)
        {
	    // super(new BorderLayout());
	    super();
	    BoxLayout layout = new BoxLayout(this,BoxLayout.Y_AXIS);
	    setLayout(layout);

	    add(createSavingBar());

	    add(createResolveQuitBar());
	    
            controlPanel = new JPanel(new BorderLayout());
	    settings = new MazeSettings(params);
	    @SuppressWarnings("serial")
            Action goNorth = new AbstractAction()
            {
                public void actionPerformed(ActionEvent evt)
                {
                    //
                    System.out.println("go North");
                    goNorth();
                }
            };
            JButton north = addDirection(this, labels.getString("north"), "UP", goNorth);

            @SuppressWarnings("serial")
            Action goEast = new AbstractAction()
            {
                public void actionPerformed(ActionEvent evt)
                {
                    //
                    System.out.println("go East");
                    goEast();
                }
            };

            JButton east = addDirection(this, labels.getString("east"), "RIGHT", goEast);

            @SuppressWarnings("serial")
            Action goWest = new AbstractAction()
            {
                public void actionPerformed(ActionEvent evt)
                {
                    //
                    System.out.println("go West");
                    goWest();
                }
            };
            JButton west = addDirection(this, labels.getString("west"), "LEFT", goWest);

            @SuppressWarnings("serial")
            Action goSouth = new AbstractAction()
            {
                public void actionPerformed(ActionEvent evt)
                {
                    //
                    System.out.println("go South");
                    goSouth();
                }
            };
            JButton south = addDirection(this, labels.getString("south"), "DOWN", goSouth);
	    
            controlPanel.add(north, BorderLayout.NORTH);
            controlPanel.add(west, BorderLayout.WEST);
            controlPanel.add(east, BorderLayout.EAST);
            controlPanel.add(south, BorderLayout.SOUTH);
            add(controlPanel, BorderLayout.NORTH);

	    // control display panel contains controls for display
	    // zoom , autozoom.
	    final JPanel controlDisplayPanel = new JPanel(new BorderLayout());
	    final JCheckBox showAll = new JCheckBox("show all");
	    showAll.addChangeListener(new ChangeListener()
		{
		    public void stateChanged(ChangeEvent e)
		    {
			maze.setShowAll( showAll.isSelected());
		    }
		});

            final JSlider slider = new JSlider(2, 40);
            slider.addChangeListener(new ChangeListener()
            {
                public void stateChanged(ChangeEvent e)
                {
                    setWallSize(slider.getValue());
                }
            });
	    final JCheckBox autoSlide = new JCheckBox("autoslide");
	    autoSlide.addChangeListener(new ChangeListener()
		{
		    public void stateChanged(ChangeEvent e)
		    {
			setAutoSize( autoSlide.isSelected());			
		    }
		});	   

	    resizecontrol = new JPanel(new FlowLayout());
	    resizecontrol.add(showAll);
            resizecontrol.add(slider);
	    resizecontrol.add(autoSlide);    

	    add(controlDisplayPanel, BorderLayout.SOUTH);
	    add(resizecontrol,BorderLayout.WEST);
	    add(settings,BorderLayout.EAST);

        }

	private JPanel getMoveControl()
	{
	    return this;
	}

	private JPanel getResizeControl()
	{
	    return resizecontrol;
	}

	private JPanel getGenerationControl()
	{
	    return settings;
	}

	public MazeSettings getSettings()
	{
	    return settings;
	}

    }

    private JButton addDirection(final JPanel panel, final String direction, String key, Action goAction)
    {
        String actionName = "go" + direction;
        JButton button = new JButton(direction);
        button.addActionListener(goAction);
        KeyStroke keystroke = KeyStroke.getKeyStroke(key);
	// attach keys to maze
        maze.getInputMap().put(keystroke, actionName);
        maze.getActionMap().put(actionName, goAction);
        return button;
    }

    private static class MazeCellParameters
    {
	double width = 10; // width of one cell
        double height = 10; // height of one cell
        int offsetX = 5; // x offset of upper corner left
        int offsetY = 5; // y offset of upper corner left

	int mapWidth = 0;
	int mapHeight = 0;

	public MazeCellParameters(int mapw, int maph, int W, int H, int x, int y)
	{
            double w = ( W - x )/ mapw;
            double h = ( H - y )/ maph;
	    mapWidth =  mapw;
	    mapHeight =  maph;
            if (w < 5)
                w = 5;
            if (h < 5)
                h = 5;
	    setCellSize(w,h);
	    offsetX = x;
	    offsetY = y;
	}

	public void resetMazeWidthHeight(int mapw, int maph)
	{
	    mapWidth =  mapw;
	    mapHeight =  maph;
	}
	
	public void adaptTo(double W, double H)
	{
            double w = ( W - offsetX ) / mapWidth;
            double h = ( H - offsetY ) / mapHeight;
	    mapWidth =  mapWidth;
	    mapHeight =  mapHeight;
            if (w < 5)
                w = 5;
            if (h < 5)
                h = 5;
	    setCellSize(w,h);
	}

	public void setCellSize(double w, double h)
	{
	    width = w;
	    height = h;	    	    
	}

	public double getWidth()
	{
	    return width;
	}

	public double getHeight()
	{
	    return height;
	}

	public int getOffsetX()
	{
	    return offsetX;
	}

	public int getOffsetY()
	{
	    return offsetY;
	}

	public Dimension getDimension()
	{
	    return new Dimension(offsetX + (int) (mapWidth * width),
				 offsetY + (int) (mapHeight * height));
	}

	public void drawWalls(Graphics g, int pX, int pY, short walls)
        {
	    int x = offsetX + (int) (pX * width);
	    int y = offsetY + (int) (pY * height);
            if ( (pY == 0) && ((walls & Brick.UP) == Brick.UP))
                g.drawLine(x, y, x + (int) width, y);
            if ((walls & Brick.DOWN) == Brick.DOWN)
                g.drawLine(x, y + (int) height, x + (int) width, y + (int) height);
            if ((walls & Brick.RIGHT) == Brick.RIGHT)
                g.drawLine(x + (int) width, y, x + (int) width, y + (int) height);
            if ( (pX == 0 ) && ((walls & Brick.LEFT) == Brick.LEFT))
                g.drawLine(x, y, x, y + (int) height);
        }

	public void drawPath(Graphics g, DirectionPosition dp, int pX, int pY, int mX, int mY)
        {
	    if ( dp != null )
		{
		    Position dot = dp.getPosition();
		    if ((dot.getX() >= pX) && (dot.getY() >= pY) && (dot.getX() < mX) && (dot.getY() < mY))
			{
			    int x = offsetX + (int) (dot.getX() * width);
			    int y = offsetY + (int) (dot.getY() * height);
			    short path = dp.getDirection();
			    int xm = x+ (int) (width / 2); 
			    int ym = y+ (int) (height / 2);
			    if ( (path & LabyModel.HORIZONTAL) == LabyModel.HORIZONTAL)
				{
				    if ( (path & LabyModel.POSITIVE) == LabyModel.POSITIVE )
					{
					    g.drawLine(xm, ym, x + (int) width, ym);
					    g.drawLine(xm, ym + (int) (height / 4), x + (int) width, ym);
					}
				    // LEFT /_
				    if ( (path & LabyModel.NEGATIVE) == LabyModel.NEGATIVE )
					{
					    g.drawLine(x, ym, xm, ym);
					    g.drawLine(x, ym, xm, ym - (int) (height / 4));
					}		
				}
			    if ((path & LabyModel.VERTICAL) == LabyModel.VERTICAL)
				{		   
				    if ( (path & LabyModel.POSITIVE) == LabyModel.POSITIVE )
					{
					    g.drawLine(xm, ym, xm , y + (int) height);
					    g.drawLine(xm - (int) (width / 4), ym , xm, y + (int) height);
					}
				    // UP |\				    				    
				    if ( (path & LabyModel.NEGATIVE) == LabyModel.NEGATIVE )
					{
					    g.drawLine(xm, ym, xm , y);
					    g.drawLine(xm + (int) (width /4), ym , xm , y);
					}
				}
			}
		}
        }

	public void drawDot(Graphics g, Position dot, int pX, int pY, int mX, int mY)
        {
            double radius = (height > width) ? width : height;
            int a = (int) ( radius / 4 );
            if ((dot.getX() >= pX) && (dot.getY() >= pY) && (dot.getX() < mX) && (dot.getY() < mY))
            {
                int x = offsetX + (int) (dot.getX() * width);
                int y = offsetY + (int) (dot.getY() * height);
		int r2 = (int) ( (radius * 3)/4 );
                g.drawOval(x + 1, y + 1, r2, r2);
                // g.drawLine(x+a,y+a,x+width-a,y+height-a);
                // g.drawLine(x+a,y+height-a,x+width-a,y+a);

            }
            else
            {
                int x = offsetX + (int) (pX * width);
                int y = offsetY + (int) (pY * height);
                g.drawLine(x + 1, y + 1, x + (int) width - 1, y + (int) height - 1);
            }
        }

    }

    private static class MazeComponent
	extends JComponent
	implements MazeCreationListener,
		   MazeResolutionListener,
		   MouseMotionListener			     
    {
        private static final long serialVersionUID = 3163272907991176390L;
	
        // WallsProvider map;
	LabyModel map;
	final MazeCellParameters cp;
        Position current = null;
        LinkedList<DirectionPosition> solvedPath = null;
	LinkedList<DirectionPosition> drawingPath = null;
        final Object lockChange = new Object();
        // current postion of human resolving
        int sX = 0;
        int sY = 0;
        // goal exit
        int gX = -1;
        int gY = -1;
	
	// not set by default for debug, will show any path
	boolean showAll = false;
	
	// for a given (x,y) pixel return cell position.
	Position getPosition(int x, int y)
	{
	    int pX = (int) ((double) (x - cp.getOffsetX()) / cp.getWidth());
            int pY = (int) ((double) (y - cp.getOffsetY()) / cp.getHeight());
	    return new Position(pX,pY);	    
	}
	
	@Override
	public void mouseDragged(MouseEvent e) {
	    // should find the cell ...
	    DirectionPosition last = null;
	    short path = 0;
	    Position newPosition = getPosition(e.getX(),e.getY());
	    if (drawingPath == null )
		{
		    drawingPath = new LinkedList<>();
		    last = new DirectionPosition((short) 0,newPosition);
		    System.out.println("Mouse dragged Cell " + newPosition + " Button  " + e.getModifiersEx() + " " + InputEvent.BUTTON1_DOWN_MASK);
		    drawingPath.addLast(last);
		}
	    else
		{
		    // setShowAll(true);
		    DirectionPosition first = drawingPath.getLast();
		    last = first;
		    // construct as many move form last to new position as needed.
		    while ( ! last.getPosition().equals(newPosition))
			 {
			     path=LabyModel.getDirection(last.getPosition(),newPosition);
			     last.setDirection(path);
			     // button 1 : add direction; button 2 : replace with direction.
			     if ( ( e.getModifiersEx() & InputEvent.BUTTON1_DOWN_MASK ) != 0 )
				 {
				     map.addDirection(last.getPosition().getX(),last.getPosition().getY(),path);
				 }
			     else
				 {
				     map.setDirection(last.getPosition().getX(),last.getPosition().getY(),path);
				 }
			     last = last.moveToAdjacentDirection();
			     drawingPath.addLast(last);
			 }
		    System.out.println("Mouse dragged from Cell " + first.getPosition() + "To" + newPosition + " Button  " + e.getModifiersEx() + " " + InputEvent.BUTTON1_DOWN_MASK);
		}
	    changed(null,null,map);
	}

	@Override
	public void mouseMoved(MouseEvent e) {
	    // System.out.println("Mouse moved (" + e.getX() + ',' + e.getY() + ')');
	    Position newPosition = getPosition(e.getX(),e.getY());
	    if ( ( newPosition.getX() >= 0 ) && ( newPosition.getY() >= 0 ) )
		{
		    requestFocus();
		}
	}

	public void setShowAll(boolean showall )
	{
	    this.showAll = showall;
	    System.out.println(showAll ? "show all" : "X");
	}
	
        void checkExit()
        {
            if ((sX == gX) && (sY == gY))
            {
                System.out.println("Exit found by human !");
            }
        }

        void goNorth()
        {
            resetResolution();
            if ((map.getWalls(sX, sY) & Brick.UP) == 0)
            {
                sY = sY - 1;
                // should redraw ...
                invalidate();
                repaint();

                checkExit();
            }

        }

        void goSouth()
        {
            resetResolution();
            if ((map.getWalls(sX, sY) & Brick.DOWN) == 0)
            {
                sY = sY + 1;
                // should redraw ...
                invalidate();
                repaint();

                checkExit();
            }

        }

        void goEast()
        {
            resetResolution();
            if ((map.getWalls(sX, sY) & Brick.RIGHT) == 0)
            {
                sX = sX + 1;
                // should redraw ...
                invalidate();
                repaint();

                checkExit();
            }
        }

        void goWest()
        {
            resetResolution();
            if ((map.getWalls(sX, sY) & Brick.LEFT) == 0)
            {
                sX = sX - 1;
                // should redraw ...
                invalidate();
                repaint();

                checkExit();
            }

        }

	/**
         width, height of one cell, 
	 offsetX, offsetY of upper left corner
	 **/	
        // public MazeComponent(WallsProvider map, MazeCellParameters cp)
	public MazeComponent(LabyModel map, MazeCellParameters cp)
        {
            super();
	    this.cp = cp;
            this.map = map;
            setPreferredSize(cp.getDimension());
            gX = map.getWidth() - 1;
            gY = map.getHeight() - 1;
        }

	// public void resetWallsProvider(WallsProvider map)
	public void resetWallsProvider(LabyModel map)
	{
	    this.map = map;
	    solvedPath=null;
	    // could be kept
	    drawingPath=null;
	    sX=0;
	    sY=0;
	    cp.resetMazeWidthHeight(map.getWidth(),map.getHeight());
	    setPreferredSize(cp.getDimension());
	}

        public void setWallSize(int size)
        {
	    cp.setCellSize((double) size,(double) size);
            // should redraw ...
            invalidate();
            repaint();
        }

	public int getAutoSize()
	{
	    Rectangle r = getBounds();
	    cp.adaptTo((int) r.getWidth(),(int) r.getHeight());
	    // should redraw ...
            invalidate();
            repaint();
	    return 50;
	}

        public void changed(Position cl, Position cr, WallsProvider map)
        {
            // should redraw ...
            invalidate();
            repaint();
            Object waiter = new Object();
            synchronized (waiter)
            {
                try
                {
                    waiter.wait(10);
                }
                catch (InterruptedException e)
                {
                    System.err.println("Interrupted !");
                }
            }
        }

        @Override
        protected void paintComponent(Graphics g)
        {
            super.paintComponent(g);
            int x = 0;
            int y = 0;
            short walls = 0;
	    short path = 0;

            // try to display only visible part...
            // should compute pX, pY, mX, mY based on clip...
            // Shape s=g.getClip();
            Rectangle r = g.getClipBounds();

            int mX = (int) ((double) r.getWidth() / cp.getWidth());
            int mY = (int) ((double) r.getHeight() / cp.getHeight());
            int pX = (int) ((double) (r.getX() - cp.getOffsetX()) / cp.getWidth());
            int pY = (int) ((double) (r.getY() - cp.getOffsetY()) / cp.getHeight());

            if (pX < 0)
                pX = 0;
            if (pY < 0)
                pY = 0;
            if (pX >= map.getWidth())
                return;
            if (pY >= map.getHeight())
                return;

            mX = mX + pX;
            mY = mY + pY;

            if (mX > map.getWidth())
            {
                mX = map.getWidth();
            }
            if (mY > map.getHeight())
            {
                mY = map.getHeight();
            }

            if ((sX == gX) && (sY == gY))
            {
                g.setColor(Color.red);
            }
            else
            {
		g.setColor(Color.green);
		cp.drawDot(g, new Position(gX, gY), pX, pY, mX, mY);
                g.setColor(Color.blue);
            }
            cp.drawDot(g, new Position(sX, sY), pX, pY, mX, mY);

            synchronized (lockChange)
            {
                g.setColor(Color.red);
                if (current != null)
                {
                    cp.drawDot(g, current, pX, pY, mX, mY);
                }
                if (solvedPath != null)
                {
                    for (DirectionPosition resolved : solvedPath)
                    {
                        // cp.drawDot(g, resolved.getPosition(), pX, pY, mX, mY);
			cp.drawPath(g,resolved, pX, pY, mX, mY);
                    }
                }
            }

	    int aX = pX;
	    int aY = pY;
	    
            g.setColor(Color.black);

	    // draw all walls within clip bounds horiz first then lines
            for (; pY < mY; pY++)
            {
                for (pX = 0; pX < mX; pX++)
                {
                    walls = map.getWalls(pX, pY);
                    cp.drawWalls(g, pX, pY, walls);
                }
            }

	    if (this.showAll)
		{
		    System.out.println("*");
		    pX = aX;
		    pY = aY;
	    
		    // draw all path within clip bounds horiz first then lines
		    for (; pY < mY; pY++)
			{
			    for (pX = 0; pX < mX; pX++)
				{
				    path = map.getPath(pX,pY);
				    if ( ( path & LabyModel.SOLVED ) == LabyModel.SOLVED )
					{
					    g.setColor(Color.green);
					}
				    else
					{
					    g.setColor(Color.blue);
					}
				    if ( path != 0 )
					{
					    DirectionPosition dp = new DirectionPosition(path,new Position(pX,pY));
					    cp.drawPath(g,dp,pX,pY,pX+1,pY+1);
					}
				}
			}
		}
	}


        public boolean notifySearch(DirectionPosition pPosition)
        {
            synchronized (lockChange)
            {
                current = pPosition.getPosition();
            }
            // should redraw ...
            invalidate();
            repaint();
            return true;

        }

        public void notifySearchError(String error)
        {
            System.err.println(error);
        }

        public void notifyCompletion(LinkedList<DirectionPosition> solvedPath)
        {
            LinkedList<DirectionPosition> newPath = new LinkedList<>(solvedPath);
            System.out.println("resolution completed");
            synchronized (lockChange)
            {
                this.solvedPath = newPath;
            }
            // should redraw ...
            invalidate();
            repaint();
        }

        public void resetResolution()
        {
            solvedPath = null;
        }
    }

    private static void setupDisplay(LabyModel model, int W, int H, MazeParams params)
    {
	Display display = new Display(model, W,H,params);
    }

    public static void save(MazeParamsFixed params,LabyModel model)
    {
	File outfile = new File(params.getSaveDir(), params.getName() + ".raw");
	if (!outfile.exists())
            {
                System.out.println("Saving to " + outfile + " ...");
                try
		    {
			FileOutputStream out = new FileOutputStream(outfile);
			model.streamOut("raw", out);
			out.flush();
			out.close();
			System.out.println("... Done.");
		    }
                catch (IOException io)
		    {
			io.printStackTrace(System.err);
		    }
            }
	else
            {
                System.out.println("" + outfile + " already exists");
            }
	outfile = new File(params.getSaveDir(), params.getName() + ".stl");
	if (!outfile.exists())
            {
                System.out.println("Saving to " + outfile + " ...");
                try
		    {
			FileOutputStream out = new FileOutputStream(outfile);
			Wall3d.streamWallsOut(params.getName(), (WallsProvider) model, out);
			out.flush();
			out.close();
			System.out.println("... Done.");
		    }
                catch (IOException io)
		    {
			io.printStackTrace(System.err);
		    }
            }
	else
            {
                System.out.println("" + outfile + " already exists");
            }
	
    }

    public static void main(String pArgs[])
    {
        LabyModel model = null;
        int W = 600;
        int H = 400;

	System.out.println("Default Locale " +  Locale.getDefault());

        if ( (pArgs.length > 0) && (pArgs[0].length() > 0))
        {
            try
            {
                model = new LabyModel("raw", new FileInputStream(pArgs[0]));
            }
            catch (IOException io)
            {
                io.printStackTrace(System.err);
		System.exit(1);
            }

	    setupDisplay(model, W,H,null);
        }
        else
        {
            MazeParamsFixed params = new MazeParamsFixed(new File("lab"),20,20,12);
            model = new LabyModel(params, new java.util.Random());

	    setupDisplay(model,W,H,params);

	    /*
            model.generateWithEntry(0, 0);

            model.addEntryOrExit(-1, 0);
            model.addEntryOrExit(params.getWidth(), params.getHeight() - 1);

            System.out.println("Generation completed");
	    */
	    /*
	    */
        }

    }
}
