/**
 * sudoku1.java
 *
 * Génère une grille de sudoku
 * par permutations de lignes ou de colonnes
 * de dimension 4x4, 9x9, 16x16 ou 25x25
 * Jean-Paul Quelen, 27 août 2010
 * petites modifications et ajout de main() le 08/01/18, modifié le 06/08/22, 21/08/22
 */ 

import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import java.util.*;

public class sudoku1 extends JPanel implements ActionListener, FocusListener {
	static final long serialVersionUID = 220821L;
	Random rnd = new Random ();
	JButton gg = new JButton ("Init");
	JTextField cv= new JTextField (Integer.toString (150), 5);
	JButton sol = new JButton ("Solution");
	JButton bF = new JButton ("F");
	JLabel lcompteur = new JLabel ("", 10);
	Thread trace;

/* n0 = 2 pour une grille 4 x 4, n0 = 3 pour une grille 9 x 9
 * n0 = 4 pour une grille 16 x 16, n0 = 5 pour une grille 25 x 25
 * les lettres ABCD... sont alors utilisés en complément
 */
	int n0 = 4;
	int n = n0 * n0;
	int n2 = n * n;
	int [][] grille = new int [n][n];
	JTextField [][] tfgrille = new JTextField [n][n];

/* F est la fonte utilisée pour l'affichage des chiffres
 * f est utilisée par le joueur. Le bouton F permet de passer
 * de la fonte f à la fonte F
 * à modifier suivant les dimensions de la fenêtre de l'applet
 * et de la valeur de n0
 */
	Font F = new Font ("Arial, Helvetica, sans-serif", Font.PLAIN, 30);
	Font f = new Font ("Arial, Helvetica, sans-serif", Font.PLAIN, 12);
	int marge = 20;
	JPanel p;
	JTextField tfgrilleij;
	String lsymb = "123456789ABCDEFGHIJKLMNOP"; // grille 4 x 4 à 25 x 25
	int gw, gh;

	public void init() {
		setLayout (new BorderLayout ());
		setFont (new Font ("Arial, Helvetica, sans-serif", Font.PLAIN, 12));

		p = new JPanel ();
		p.setBackground (Color.WHITE);
		add (p, BorderLayout.NORTH);

		p.add (gg);
		gg.addActionListener (this);

		p.add (cv);

		JLabel lb = new JLabel ("cases visibles.");
		p.add (lb);
		lb.setBackground (Color.WHITE);

		p.add (bF);
		bF.addActionListener (this);

		p.add (sol);
		sol.addActionListener (this);

		p.add (lcompteur);

		grille [0][0] = -1;

// montage de la grille affichée
// un JPanel avec une grille de dimension n0 x n0.
// chaque case contient n0 x n0 cases de type JTextField
		p = new JPanel ();
		p.setLayout (new GridLayout (n0, n0, 1, 1));
		add (p, BorderLayout.CENTER);
		JPanel [][] pn0 = new JPanel [n0][n0];
			for (int i = 0; i < n0; i ++) {
				for (int j = 0; j < n0; j ++) {
					p.add (pn0 [i][j] = new JPanel ());
					pn0 [i][j].setLayout (new GridLayout (n0, n0));
				}
			}
		for (int i = 0; i < n; i ++)
			for (int j = 0; j < n; j ++)
				pn0 [i / n0][j / n0].add (tfgrille [i][j] = new JTextField (" "));
//int npx = (Math.min (getWidth (), getHeight ()) - marge - marge) / n;
//	F = new Font ("Arial, Helvetica, sans-serif", Font.PLAIN, npx);
//	f = new Font ("Arial, Helvetica, sans-serif", Font.PLAIN, npx / 3);
		for (int i = 0; i < n; i ++) {
			for (int j = 0; j < n; j ++) {
				tfgrille[i][j].setFont(F);
				tfgrille[i][j].addFocusListener (this);
			}
		}
	}

	public void echange () {
// choix de 2 lignes ou colonnes à échanger (dans le même bloc)
		int k = rnd.nextInt (n);
		int l = k - k % n0 + (k + rnd.nextInt (n0 - 1) + 1) % n0;
		if (rnd.nextBoolean ()) {
			for (int j = 0; j < n; j ++) {
				int gkj = grille [k][j];
				grille [k][j] = grille [l][j];
				grille [l][j] = gkj;
			}
		} else {
			for (int i = 0; i < n; i ++) {
				int gik = grille [i][k];
				grille [i][k] = grille [i][l];
				grille [i][l] = gik;
			}
		}
	}

	public void actionPerformed (ActionEvent evt) {
		if (evt.getSource() == gg) {
			if (gg.getText().compareTo("Stop") == 0) {
// génération de la grille terminée, on affiche des cases
				sol.setEnabled (true);
				bF.setEnabled (true);
				gg.setText ("Init");

				for (int i = 0; i < n; i ++)
					for (int j = 0; j < n; j ++) {
						JTextField tfgrilleij = tfgrille [i][j];
						tfgrilleij.setFont (F);
						tfgrilleij.setForeground (Color.BLACK);
						tfgrilleij.setText (" ");
					}

				int compteur = 0;
				try {
					compteur = Integer.parseInt (cv.getText ());
				}
				catch (NumberFormatException e) {}
				if (compteur > n2) {
					cv.setText (Integer.toString (n2));
					afficheSolution ();
				} else {
					if (compteur <= 0)
						cv.setText ("0");
					while (compteur > 0) {
						int i = rnd.nextInt (n);
						int j = rnd.nextInt (n);
						String s = tfgrille[i][j].getText();
						if (s.compareTo (" ") == 0) {
							tfgrille[i][j].setText (lsymb.substring (grille[i][j], grille[i][j] + 1));
							compteur --;
						}
					}
				}
			} else {
// génération d'une grille
				sol.setEnabled (false);
				bF.setEnabled (false);
				gg.setText ("Stop");
				for (int i = 0; i < n; i ++)
					for (int j = 0; j < n; j ++)
						grille [i][j] = (i % n0 * n0 + i / n0 + j) % n;

				Thread t = new ginit();
				t.start();

			}
		} else
			if (evt.getSource () == sol)
				afficheSolution ();
		else if (evt.getSource () == bF) {
// un appui sur le bouton F met le contenu de la case
// sur laquelle il y a le focus en gros caractères
			if (tfgrilleij != null) {
				tfgrilleij.setFont (F);
			 tfgrilleij.setText(tfgrilleij.getText().trim());
			}
		}
	}

	private void afficheSolution () {
		if (grille [0][0] != -1) {
			for (int i = 0; i < n; i ++) {
				for (int j = 0; j < n; j ++) {
					if (tfgrille[i][j].getText().trim().length() == 0) {
						tfgrille[i][j].setForeground (Color.BLUE);
					}
					tfgrille [i][j].setText (lsymb.substring (grille [i][j], grille [i][j] + 1));
					tfgrille [i][j].setFont (F);
				}
			}
		}
	}

	public void focusGained (FocusEvent e) {
		if (gg.getText().compareTo ("Init") == 0) {
			tfgrilleij = (JTextField) e.getSource ();
			tfgrilleij.setFont (f);
			tfgrilleij.setForeground (Color.BLUE);
		}
	}

	public void focusLost (FocusEvent e) {}

	public static void main (String [] args) {
		int w = 650;
		int h = 650;

		sudoku1 s = new sudoku1();
//		s.gw = 400;
//		s.gh = 400;
		s.init();

		JFrame jf = new JFrame ("sudoku1");
		jf.setDefaultCloseOperation (JFrame.EXIT_ON_CLOSE);
		jf.add (s);
		jf.setSize (w, h);
		jf.setVisible (true);
	}

/**
 * classe : ginit
 * echange des lignes ou colonnes jusqu'à appui sur le bouton "stop"
 */

protected class ginit extends Thread {

	public void run() {
		int compteur = 0;
		while (gg.getText().compareTo ("Stop") == 0) {
			echange();
			compteur++;
			lcompteur.setText (Integer.toString (compteur));
			try {
				Thread.sleep (0L);
			}
			catch (InterruptedException e) {}
		}
	}
}

}
