package fanch.multijeu.sudoku.data;

/**
 * Created by Francois Gautrais on 16/10/14.
 * Gestion (génération, résolution, détections d'erreurs) de sudoku
 */

import java.util.Random;

public class OldSudoku {
    public class Case
    {
        public int x,y;
        public byte c;
        Case(int _x, int _y, byte _c){x=_x; y=_y; c=_c;}
    }
    private final static int N=9;
    private final static int N_TOTAL=81;
    private static Random generator = new Random(System.currentTimeMillis());

    protected byte grid[] = new byte[N*N];
    private int nb_try=0;
    private long time=0;
    protected String mDefault="";

    public static final int DONE=0;
    public static final int VERY_VERY_EASY=20;
    public static final int VERY_EASY=30;
    public static final int EASY=40;
    public static final int NORMAL=45;
    public static final int HARD=50;
    public static final int VERY_HARD=55;
    public static final int VERY_VERY_HARD=60;

    private OldSudoku resolved=null;

    public int getNb_try() {
        return nb_try;
    }


    /**
     * Remplie une case non remplie jusque la
     */
    public Case help()
    {
        _resolve();
        int tmp[] = new int[N_TOTAL];
        int i,j=0,k;
        for(i=0; i<1000; i++)
        {
            int x=randint(0,8);
            int y=randint(0,8);
            if(get(x,y)==0)
                return new Case(x,y,resolved.get(x,y));
        }
        return null;
    }

    /**
     * Met a jour le champs resolved
     */
    private void _resolve()
    {
        if(resolved==null)
        {
            resolved=new OldSudoku(toString());
            resolved.resolve();
        }
    }

    /**
     * Retourne les erreurs
     * @param str La chaine qui contient le sudoku a verifier
     * @return La chaine (1=erreur, 0=ok)
     */
    public String errors(String str)
    {
        String ret="";
        _resolve();
        String ok=resolved.toString();
        int k=Math.min(ok.length(), str.length());
        for(int i=0; i<k; i++)
        {
            int a=str.charAt(i)-'0', o=ok.charAt(i)-'0';
            if(a==0 || a==o) ret+='0';
            else ret+='1';
        }
        return ret;
    }

    public void setNb_try(int nb_try) {
        this.nb_try = nb_try;
    }

    public long getTime() {
        return time;
    }

    public void setTime(long time) {
        this.time = time;
    }

    /**
     * Remplie la grille de zero
     */
    private void clear()
    {
        int i;
        for(i=0; i<N_TOTAL; i++) grid[i]=0;
    }

    /**
     * Modifie la valeur de la case
     * @param i Ligne
     * @param j Colonne
     * @param c La valeur de la case (si =0: non remplie)
     */
    public void set(int i, int j, byte c)
    {
        grid[j*N+i]=c;
    }


    public void set(int i, byte c)
    {
        grid[i]=c;
    }

    public OldSudoku copy()
    {
        return new OldSudoku(toString());
    }

    /**
     * Recupere la valeur de la grille
     * @param i Ligne
     * @param j Colonne
     * @return La valeur de la case(si =0: non remplie)
     */
    public byte get(int i, int j)
    {
        return grid[j*N+i];
    }

    /**
     * Recupere la valeur de la grille
     * @param i le nombre
     * @return La valeur de la case(si =0: non remplie)
     */
    public byte get(int i)
    {
        return grid[i];
    }

    /**
     * Verifie si la colonne c est valide pour la case (x,y)
     * @param x Adresse de la colonne
     * @param c Solution propose
     * @return False si la solution est bonne, sinon true
     */
    private boolean check_column( int x,  byte c)
    {
        int i, j;
        for(i=0; i<9; i++)
            if(get(x, i)==c) return true;
        return false;
    }

    /**
     * Verifie si la ligne c est valide pour la case (x,y)
     * @param y Adresse de la ligne
     * @param c Solution propose
     * @return False si la solution est bonne, sinon true
     */
    private boolean check_line( int y,  byte c)
    {
        int i, j;
        for(i=0; i<9; i++)
            if(get(i, y)==c) return true;
        return false;
    }

    /**
     * Verifie si le carre c est valide pour la case (x,y)
     * @param x Adresse de la case
     * @param y Adresse de la case
     * @param c Solution propose
     * @return False si la solution est bonne, sinon true
     */
    private boolean check_square(int x,  int y,  byte c)
    {
        int dx,  dy;
        int i, j;
        if(x>=6) dx=6; else if(x>=3) dx=3; else dx=0;
        if(y>=6) dy=6; else if(y>=3) dy=3; else dy=0;
        for(i=dx; i<dx+3; i++)
            for(j=dy; j<dy+3; j++)
                if(x!=i && j!=y && get( i, j)==c) return true;
        return false;
    }

    /**
     * Verifie si la solution c est valide pour la case (x,y)
     * @param x Adresse de la case
     * @param y Adresse de la case
     * @param c Solution propose
     * @return True si la solution est bonne, sinon false
     */
    public boolean check(int x, int y, byte c)
    {

        return !(check_line(y,c) || check_column(x,c)
                || check_square(x, y, c));
    }

    public byte resolved(int x, int y)
    {
        return resolved.get(x, y);
    }


    public byte resolved(int i)
    {
        return resolved.get(i);
    }



    /**
     * Genere un entier aleatoirement dans l'interval [x .. y]
     * avec x>=y
     * @param x Minimum
     * @param y Maximum
     * @return L'entier
     */
    private int randint(int x,  int y)
    {
        return (int) ((Math.random()*(y-x+1))+x);
    }


    /**
     * Tente d'inserer une valeur pseudo aleatoire respectant les
     * contraintes du jeu dans une case precise
     * @param x Adresse de la case
     * @param y Adresse de la case
     * @return -1 si aucune solutions n'est possible
     * 			Sinon la valeur inserer
     */
    private byte insert(int x,  int y)
    {
        int i, j=0;
        byte c=-1;
        byte tmp[]= new byte[N];
        for(i=0; i<9; i++)
            if(check( x, y, (byte) (i+1)))
                tmp[j++]=(byte)(i+1);

        if(j==0) return -1;
        c=tmp[randint(0, j-1)];
        set(x, y, c);
        return c;
    }


    /**
     * Renvoie le nombre d'erreur dans le sudoku
     * @return Nombre d'erreur dans le sudoku (si =0 sudoku valide)
     */
    private int nbfail()
    {
        int i, j=0;
        for(i=0; i<81; i++)
            j+=((get(i%9, i/9 )==0)?1:0);
        return j;
    }

    /**
     * Cherche une combinaison de sudoku de maniere stochastique
     * @param clear Si true: la grille est vider (mise a 0)
     * @return le nombre d'erreur (si =0 le sudoku est valide)
     */
    private int search_sudoku(boolean clear)
    {
        int i,j;
        if(clear) clear();
        for(j=0; j<9; j++)
        {
            for(i=0; i<9; i++)
                if(insert(i, j)<0) break;
        }
        return nbfail();
    }

    /**
     * Cherche une combinaison de sudoku de maniere stochastique
     * @return le nombre d'erreur (si =0 le sudoku est valide)
     */
    private int search_sudoku()
    {
        return search_sudoku(true);
    }

    /**
     * Genere une grille de sudoku
     * @param max Le nombre maxial d'iteration (avant echec)
     * @return -1 si la generation a echoue
     * 			Sinon, le nombre d'essais
     */
    protected int find_sudoku(int max)
    {
        long a=System.currentTimeMillis();
        if(max==0) max=10000;
        int i=0,x=1;
        for(i=0; i<max && x>0; i++)
            x=search_sudoku();
        if(x>0) i=-1;
        time=System.currentTimeMillis()-a;
        nb_try=i;
        return i;
    }

    /**
     * Genere une grille de sudoku
     * @return -1 si la generation a echoue
     * 			Sinon, le nombre d'essais
     */
    protected int find_sudoku()
    {
        int x = find_sudoku(0);
        mDefault=toString();
        resolved=new OldSudoku(toString());
        return x;
    }

    protected OldSudoku(){}

    /**
     * Cree un sudoku a partir d'un chaine
     *
     * @param str Le sudoku:  suite de 81 numeros ('0':case vide, '1-9' numero)
     */
    public OldSudoku(String str)
    {
        loadFromString(str);
        mDefault=toString();
    }

    public void loadFromString(String str)
    {
        for(int i=0; i<N_TOTAL; i++)
        {
            byte b = (byte) (str.charAt(i) - '0');
            if(b<0 || b>N) b=0;
            set(i%N,i/N,b);
        }
    }

    public void reset()
    {
        loadFromString(mDefault);
    }

    public boolean isDefault(int x, int y)
    {
        if(mDefault==null || mDefault.length()<81) return false;
        return mDefault.charAt(y*9+x)!='0';
    }

    /**
     * Met une case aleatoire a 0
     * (200 essais max)
     */
    protected boolean clearRandom()
    {
        if(nbfail()==N_TOTAL-1) return false;
        int i,x;
        for(i=0; i<200; i++)
        {
            x=randint(0, N_TOTAL-1);
            if(grid[x]==0) continue;
            grid[x]=0;
            return true;
        }
        return false;
    }

    /**
     * Met a 0 des case aleatoirement
     * @param n Nombre de case a mettre a 0
     */
    protected void clearRandom(int n)
    {
        for(int i=0; i<n; i++)
            clearRandom();
    }

    /**
     * Permet de savoir si le sudoku est resolu
     * @return true si il est resolu sinon false
     */
    public boolean isResolved()
    {
        int i, j;
        for(j=0; j<N; j++)
            for(i=0; i<N; i++)
                if(check(i,j, get(i,j)) || get(i,j)==0) return false;
        return true;
    }

    /**
     * Affiche le sudoku: suite de 81 numeros ('0':case vide, '1-9' numero)
     * @return la fameuse chaine
     */
    public String toString()
    {
        String s="";
        for(int i=0; i<N_TOTAL; i++)
            s+=""+get(i%N, i/N);
        return s;
    }

    /**
     * Affiche le sudoku sur la sortie standart
     */
    public void print_grid()
    {
        int i, j;
        for(j=0; j<9; j++)
        {
            if(j==3 || j==6) System.out.print("______________________\n");
            for(i=0; i<9; i++)
            {
                if(i==3 || i==6) System.out.print("| ");
                if(get( i, j)>0) System.out.print(get( i, j)+" ");
                else System.out.print("- ");
            }
            System.out.print("\n");
        }
    }

    /**
     * Savoir si une chaine de sudoku est resolue
     * @param str Le sudoku: suite de 81 numeros ('0':case vide, '1-9' numero)
     * @return La string de meme format que le parametre, mais resolue !
     * 		   Si le sudoku n'est pas resolvable: null
     */
    public static  boolean isResolved(String str)
    {
        OldSudoku s = new OldSudoku(str);
        return s.isResolved();
    }

    /**
     * Resoudre le sudoku passe en parametre
     * @param str Le sudoku: suite de 81 numeros ('0':case vide, '1-9' numero)
     * @return La string de meme format que le parametre, mais resolue !
     * 		   Si le sudoku n'est pas resolvable: null
     */
    public static String resolve(String str)
    {
        OldSudoku s = new OldSudoku(str);
        boolean b = s.resolve();
        return (b)?s.toString():null;
    }

    /**
     * Genere un sudoku
     * @param k Difficulte (nombre de case a enlever)
     * @return Un objet sudoku
     */
    public static OldSudoku generate(int k)
    {
        OldSudoku s=new OldSudoku();
        int x = s.find_sudoku();
        if(k>=0)
            s.clearRandom(k);
        s.mDefault=s.toString();

        if(x>=0) return s;
        return null;
    }


    public boolean resolve()
    {
        for(int j=0; j<9; j++)
        {
            for(int i=0; i<9; i++)
            {
                if(get(i,j)==0) continue;
                byte x=(byte)get(i,j);
                set(i,j,(byte)0);
                if(!check( i, j, x))
                    return false;
                set(i,j,(byte)x);

            }
        }
       return resolverec(0);
    }

    /**
     * Resoudre le sudoku
     * @return false si le probleme en insoluble
     */
    private boolean resolverec(int k)
    {
        int x=0, y=0, i, j=0;
        byte c=-1;
        boolean b=true;
        byte tmp[]= new byte[N];

		/*
		 * Choix de la premiere case libre
		 */
        for(j=0; j<9 && b; j++)
            for(i=0; i<9 && b; i++)
                if(get(i,j)==0)
                {
                    x=i;
                    y=j;
                    b=false;
                    break;
                }

		/*
		 * S'il n'y a plus de cases libres, c'est que le sudoku
		 * est fini !!
		 */
        if(nbfail()==0) return true;
        i=x;
        j=0;

		/*
		 * Recuperer toutes les solution possible pour
		 * la case courrante
		 */
        for(i=0; i<9; i++)
            if(check( x, y, (byte) (i+1)))
                tmp[j++]=(byte)(i+1);

		/*
		 * Essayer toute les solutions possibles pour la case courrante
		 * puis appeler recursivement
		 */
        for(i=0; i<j; i++)
        {
            set(x,y,tmp[i]);
            if(resolverec(k+1)) return true;
        }

		/*
		 * Si aucune solutions n'est possible,
		 * c'est que le probleme vient d'en haut.
		 * Mettre la case a 0 et return false
		 */
        set(x,y,(byte)0);
        return false;


    }

    public static OldSudoku generate()
    {
        return generate(NORMAL);
    }

    public static OldSudoku generateFilled()
    {
        return generate(-1);
    }


}


/*

   9    1
3 26
7     96
 8 5  4
  51    7
 7   3  9
8   26 4
     169
 2  9 7



403067100000304000092000006000580304050040010806073000100000740000701000008430201


705008200001620000020100000006040830054000720087030100000004090000012300002300501


008009400000070000930200080000060908070000000490038071000006017000040890000000020

718659432
642873159
935214786
523761948
871492365
496538271
384926517
267145893
159387624
 */