/**
 * sp2run.java
 *
 * La symétrie perturbée, d'après l'ouvert n°49 de décembre 1987
 * fabrique une chaîne de 0 et de 1 (1 = g 0 = d) dans un byte[]
 * pas de récursivité
 * le calcul de la chaîne des déplacement ne se fait que si Niter, le nombre d'itérations a augmenté
 * pour éviter des calculs inutiles on copie le byte[] dans un byte[] plus grand et on poursuit les calculs
 *
 * Utilisation d'un thread pour interrompre le calcul :
 * soit en fermant la fenêtre, soit en cliquant sur le bouton "Stop",
 * soit en modifiant directement les paramètres et en cliquant dans la fenêtre.
 *
 * Attention dans cette version il n'est pas possible d'interrompre le tracé.
 *
 * Jean-Paul Quelen 02/05/20
 */

import java.awt.*;
import java.awt.event.*;
import javax.swing.*;

public class sp2run extends JPanel implements MouseListener, Runnable, ActionListener {
	static final long serialVersionUID = 200502L;
// dir =	0	1	2	3	4	5	6	7
//			N	NE	E	SE	S	SO	O	NO
	int xmax, ymax, Niter, X0, Y0, dir, Xp, Yp, NiterOld;
	double l, x, y, ls2;
	char or;
	TextField tfn, tfl, tor, lcalc;
	byte[] bits = {-128, 64, 32, 16, 8, 4, 2, 1};
	int fin;
	byte[] mvt;
	boolean stop;
	Button btstop;

	public sp2run() {
		Font f = new Font (Font.SANS_SERIF, Font.PLAIN, 10);
		Label lbl;

		add (lbl = new Label ("Orientation initiale (N, S, O, E)"));
		nlab (lbl, f);
		add (tor = new TextField ("E", 1));
		tor.setFont (f);
		or = 'E';
		dir = 2;	//"NESO".indexOf (or) * 2;

		add (lbl = new Label ("Itérations : "));
		nlab (lbl, f);
		add (tfn = new TextField ("2", 10));
		tfn.setFont (f);
		Niter = NiterOld = 2;
		mvt = new byte [1];
		mvt[0] = (byte) 0xc0;

		add (lbl = new Label ("Longueur d'un pas (en pixels)"));
		nlab (lbl, f);
		add (tfl = new TextField ("8.0", 10));
		tfl.setFont (f);
		l = 8.0;
		ls2 = 4.0;

		add (lcalc = new TextField ("", 20));
		lcalc.setFont (f);
		lcalc.setBackground (Color.black);
		lcalc.setForeground (Color.white);
		addMouseListener (this);

		add (btstop = new Button ("Stop"));
		btstop.addActionListener (this);
		btstop.setFont (f);
	}

	private void nlab (Label lbl, Font f) {
		lbl.setFont (f);
		lbl.setBackground (Color.black);
		lbl.setForeground (Color.white);
	}

	public void actionPerformed (ActionEvent ae) {
		if (ae.getSource() == btstop) {
			stop = true;
		}
	}

	public void avance (Graphics g, double l) {
		switch (dir) {
			case 1, 2, 3 :
				x += l;
				break;
			case 5, 6, 7 :
				x -= l;
		}
		switch (dir) {
			case 3, 4, 5 :
				y += l;
				break;
			case 7, 0, 1 :
				y -= l;
		}
		int X = (int) x;
		int Y = (int) y;
		if (X != Xp || Y != Yp) {
			g.drawLine (Xp, Yp, X, Y);
			Xp = X;
			Yp = Y;
		}
	}

	public void trace (Graphics g) {
		avance (g, l);
		int imvt = 0;
		int iimvt = 0;
		fin = (1 << Niter) - 1;						// nbre de bits à lire
		for (int i = 0; i < fin; i++) {
			int dg = ((mvt [imvt] & bits [iimvt]) == 0) ? 1 : 7;
			avance (g, l);
			dir = (dir + dg) & 7;			// ne pas faire (dir - 1) % 8
			avance (g, ls2);
			dir = (dir + dg) & 7;
			avance (g, l);			
			iimvt++;
			if (iimvt >= 8) {
				iimvt = 0;
				imvt++;
			}
		}
		avance (g, l);
	}

	private int dimbyte (int n) {
			if (n <= 3)
				return 1;
			return n = 1 << (n - 3);
	}
	
	public void run() {
		lcalc.setText ("Calcul en cours");
		if (Niter > NiterOld) {
			lcalc.setText ("Calcul en cours");
			int dim = dimbyte (Niter);
			int mvtl = mvt.length;
			int dimcopy = dimbyte (NiterOld);
			if (dim > mvtl) {
				byte[] mvt1 = new byte [dim];
				System.arraycopy (mvt, 0, mvt1, 0, dimcopy);
				mvt = mvt1;
			}
			for (int i = NiterOld; i < Niter; i++) {
				fin = (1 << i) - 1;			// nbre de bits à lire
				int cop = (2 << i) - 2;		// index du début de la zone de copie
				int imvt = 0;
				int iimvt = 0;
				int icop = cop / 8;
				int iicop = cop % 8;
				for (int j = 0; j < fin; j++) {
					if ((mvt [imvt] & bits [iimvt]) == 0) {
						mvt [icop] |= bits [iicop];
					}
					iimvt++;
					if (iimvt >= 8) {
						iimvt = 0;
						imvt++;
					}
					iicop--;
					if (iicop < 0) {
						iicop = 7;
						icop--;
					}
					if (stop)
						break;
				}
				mvt [icop] |= bits [iicop];	// 1 central
				if (stop)
					break;
			}
		}
		if (stop) {
			Niter = 1;
			mvt[0] = (byte) 0x80;
		}
		NiterOld = Niter;
		lcalc.setText ("");
		if (!stop)
			repaint();
	}

	public void update (Graphics g) {
		paint (g);
	}

	public void paint (Graphics g) {
		if (xmax != getWidth() || ymax != getHeight()) {
			xmax = getWidth();
			ymax = getHeight();
			X0 = xmax / 2;
			Y0 = ymax / 2;
		}
		x = Xp = X0;
		y = Yp = Y0;

		g.setColor (Color.black);
		g.fillRect (0, 0, xmax, ymax);
		g.setColor (Color.white);

		lcalc.setText ("Dessin en cours");
		trace (g);
		lcalc.setText ("");
	}

	public void mousePressed (MouseEvent e) {
		stop = true;
//récupération des paramètres
		char or1 = tor.getText().toUpperCase().charAt(0);
		if ("NSOE".indexOf (or1) != -1) {
			or = or1;
		}
		tor.setText (Character.toString (or));
		dir = "NESO".indexOf (or) * 2;

		int niter1 = Niter;
		try {
			niter1 = Integer.parseInt (tfn.getText());
			if (niter1 > 0)
				Niter = niter1;
		}
		catch (NumberFormatException nfe) {}
		tfn.setText (Integer.toString (Niter));

		double l1 = l;
		try {
			l1 = Double.parseDouble (tfl.getText());
			if (l1 > 0.0)
				l = l1;
		}
		catch (NumberFormatException nfe) {}
		tfl.setText (Double.toString (l));
		ls2 = 0.5 * l;
		X0 = e.getX();
		Y0 = e.getY();

		stop = false;
		new Thread(this).start();
	}

	public void mouseReleased (MouseEvent e) {}
	public void mouseClicked (MouseEvent e) {}
	public void mouseEntered (MouseEvent e) {}
	public void mouseExited (MouseEvent e) {}

////////////////////////////////////////////////////////////////////////////////

	public static void main (String [] args) {
		int w = 1000;
		int h = 1000;

		sp2run s = new sp2run();

		JFrame f = new JFrame ("La symétrie perturbée v2run");
		f.setDefaultCloseOperation (JFrame.EXIT_ON_CLOSE);
		f.add (s);
		f.setSize (w, h);
		f.setVisible (true);
	}

}

