(** Projet Puzzle @author Lilian Besson <lilian.bessonATens-cachan.fr> *)


let ex () = print_string "\n#use \"interface.ml\";;\n";;
let d_d s = Sys.command s;;
let print_signature () = d_d "ocamlc -i interface.ml";;

(* Ouverture des modules extérieurs *)

open List;;
open String;;
open Graphics;;


(*--------------------------------------------------------------------------------------------------------------------------------------------- *)
(** Cette interface graphique se base sur le contenu de 'resolution.ml'. Il faut noter qu'un changement dans la librairie de resolution devrait ne pas rendre cette interface obsolète, du moment que les types et les signatures des fonctions principales soient conservées. *)


(*#use "resolution.ml";;*)
open Resolution;;
if (!(Sys.interactive)) then print_string "Module Resolution : bien importé !";;
(*let ex () = print_string "\n#use \"interface.ml\";;\n";;*)

(** Utilise aussi le fichier 'temps.ml'. Pour mesurer le temps mis par une résolution !. *)

(*#use "temps.ml";;*)
open Temps;;

(*--------------------------------------------------------------------------------------------------------------------------------------------- *)
(** On doit batir une interface graphique simple sur ces fonctions là. *)

(** Les premières choses que l'on doit faire :
  • genere_echelle_couleur : doit etre capable de générer une échelle de couleurs, en lui donnant simplement le nombre de couleurs différentes. Une échelle de couleurs est une bijection entre |1; ..; c| et l'espace des couleurs, qui soit pas trop moche qui plus est;
  • afficher_piece : doit etre capable d'afficher une pièce donnée, en lui indiquant la fenètre, la position (relative : indice (i,j)), et le nombre total de pièces (m,n), et le nombre de couleurs;
  • afficher_instance : doit afficher une instance, présente dans un fichier extérieur, ou donné en argument, dans une fenètre déja définie.
  • genere_instance : doit demander des valeurs à l'utilisateur (a voir, avec Graphics, zenity ne marchera pas (car Sys.command de Ocaml renvoit le code de retour de la commande, et ne peut renvoyer de valeurs)), et générer une instance aléatoire en fonction de ces valeurs;
  • initialise_instance_graphique : doit etre capable de créer une fenètre, et d'afficher le quadrillage de 1;m x 1;n qui va bien;
Il faudra voir ensuite s'il est possible de : générer une instance, demander le nombre de pièces fixes voulues, puis de proposer à l'utilisateur de les fixer sur ce quadrillage, puis de lancer la resolution en fonction de ça

  • afficher_solution : doit pouvoir afficher une solution, après l'avoir calculée, ou bien un message expliquant pourquoi il n'y a pas de solution. Idéalement, il serait pratique de pouvoir afficher un certificat (simple, quand c'est possible) de cette non existence. Par exemple, si une des pièces n'a aucun voisin horizontal (resp. vertical), c'est une preuve de l'impossibilité de la résolution. A voir une fois que le reste sera fini;
  • browse : pareil, après avoir bien avancé, devra etre capable d'afficher un explorateur de fichiers, et de choisir l'instance que l'on veut résoudre. Il faudra aussi donner la capacité à l'utilisateur de sauvegarder une solution calculée, sous forme de 'plateau' dans un fichier texte, ou pourquoi pas sous forme d'image (capture de la fenètre) ?
  • afficher_echelle_couleur : devra etre un petit truc rapide qui affiche la bijection trouvée pour les couleurs, pour demander à l'utilisateur si elle lui conviend. Et il serait fun de pouvoir choisir entre plusieurs "genres" d'échelles de couleurs (ex : bleu -> rouge; ou noir -> blanc). A voir selon la facilité du truc;
=> BEAUCOUP DE CHOSES A FAIRE !

JE VAIS ESSAYER D'UTILISER UNIQUEMENT LE MODULE GRAPHICS. SI JE VOIT QUE C'EST TROP COMPLIQUE, OU PAS ASSEZ PRATIQUE, JE PASSERAI EN LABLGTK. (VOIR TUTO SiteDuZero). EN FAIT, PLUTOT QUE DE PASSER A UNE AUTRE LIBRAIRIE, COMME J'AVAIS DEJA BIEN AVANCÉ, J'AI UTILISE L'OUTIL "ZENITY" POUR LA PARTIE INTERACTIVE. *)


if (!(Sys.interactive)) then ex();;
(*--------------------------------------------------------------------------------------------------------------------------------------------- *)
open Graphics;;

        
        (** *)

        
        (** Fonctions outils *)

        
        (** *)


(** Pour ouvrir et mettre un titre à notre fenêtre. *)

let init_fenetre ~x ~y () =
        open_graph (" "^(string_of_int x)^"x"^(string_of_int y));
        set_window_title "Projet Puzzle";;

(** Pour récupérer les coordonnées (r,g,b) d'une couleur du module Graphics. *)

let from_rgb (c : Graphics.color)  = 
        let r = c / 65536  and  g = c / 256 mod 256  and  b = c mod 256 
        in (r,g,b) ;;

(** Pour inverser les couleurs d'une image. *)

let inv_color (c : Graphics.color) = 
        let (r,g,b) = from_rgb c 
        in Graphics.rgb (255-r) (255-g) (255-b) ;;

(** Passe d'un triplet à un tableau. *)

let array_ot_triple (x,y,z) = [|x;y;z|];;

(** Pour dessiner une rectangle. *)

let draw_rect x0 y0 w h = 
        let (a,b) = Graphics.current_point() 
        and x1 = x0+w and y1 = y0+h 
        in
        Graphics.moveto x0 y0; 
        Graphics.lineto x0 y1; Graphics.lineto x1 y1;  
        Graphics.lineto x1 y0; Graphics.lineto x0 y0; 
        Graphics.moveto a b  ;;

(** Pour écrire un entier en commancant en un pixel précis. *)

let draw_int ~x ~y ~i = 
        let back_svg = Graphics.background in
        let pt_svg = Graphics.current_point () in
        Graphics.moveto x y;
        Graphics.draw_string (string_of_int i) ;
        Graphics.set_color back_svg;
        Graphics.moveto (fst pt_svg) (snd pt_svg);;

        
        (** *)

        
        (** Fonctions de base *)

        
        (** *)


(** On définit quelques constantes, afin que toutes les fonctions suivantes utilisent la même couleur de fond, et la même couleur de tracés. *)

let couleur_fond = Graphics.white;;
let couleur_lignes = Graphics.black;;


(** Pour générer une échelle de couleurs. *)

(** On procède uniquement par des dégradés : il faut choisir deux couleurs (min et max), et le nombre de couleurs voulues, et ca nous renvoit une échelle. *)

let genere_echelle_couleur ~(cmin:Graphics.color) ~(cmax:Graphics.color) ~(nb:int) =
        let ccmin, ccmax = (array_ot_triple(from_rgb cmin), array_ot_triple(from_rgb cmax)) in
        let gamma = [| 
                float_of_int(ccmin.(0)) +. (1.0 /. (float_of_int (nb + 1))) *. float_of_int(ccmax.(0) - ccmin.(0));
                float_of_int(ccmin.(1)) +. (1.0 /. (float_of_int (nb + 1))) *. float_of_int(ccmax.(1) - ccmin.(1));
                float_of_int(ccmin.(2)) +. (1.0 /. (float_of_int (nb + 1))) *. float_of_int(ccmax.(2) - ccmin.(2)) |] in
        let echelle_resultat = Array.make nb cmin in
        for i = 1 to nb do
                echelle_resultat.(i-1) <- Graphics.rgb (abs(i * int_of_float(gamma.(0)))) (abs(i * int_of_float(gamma.(1)))) (abs(i * int_of_float(gamma.(2)))) ;
        done;
        echelle_resultat;;


(** Une échelle de gris, simple. *)

let echelle_gris nb = genere_echelle_couleur ~cmax:Graphics.white ~cmin:Graphics.black ~nb:(nb+1);;
(** Une échelle de rouge, simple. *)

let echelle_rouge nb = genere_echelle_couleur ~cmax:Graphics.red ~cmin:Graphics.black ~nb:nb;;
(** Une échelle de vert, simple. *)

let echelle_vert nb = genere_echelle_couleur ~cmax:Graphics.green ~cmin:Graphics.black ~nb:nb;;
(** Une échelle de jaune, simple. *)

let echelle_jaune nb = genere_echelle_couleur ~cmax:Graphics.blue ~cmin:Graphics.white ~nb:nb;;
(** Une échelle de bleu, simple. *)

let echelle_bleu nb = genere_echelle_couleur ~cmax:Graphics.blue ~cmin:Graphics.white ~nb:nb;;

(** Une échelle de violet. *)

let echelle_violet nb = genere_echelle_couleur ~cmax:Graphics.red ~cmin:Graphics.blue ~nb:nb;;
(** Une échelle du jaune au rouge (style métal en fusion). *)

let echelle_metal nb = genere_echelle_couleur ~cmax:Graphics.red ~cmin:Graphics.yellow ~nb:nb;;

(** *)

(** Un outil utilisant 'zenity' pour demander à l'utilisateur de rentrer deux couleurs pour l'échelle. *)

(** *)


(** Pour gérer le code de retour d'un processus Unix lancé par la commande "Sys.command" en ocaml. Ce n'est pas très très propre de faire comme ca, mais ca marche bien. *)

exception Error_Annule of int;;

(** Un peu de scripting en Ocaml. La fonction suivante execute la commande Unix contenue dans la chaine argument, en renvoyant une exception si la commande ne s'execute pas comme il faut (code d'erreur de retour unix non nul). *)

let faire cmd =
        match (Sys.command cmd) with
        | 0                        -> ();
        | n when (n <> 0)         -> raise (Error_Annule(n));;

(** Afficher une fenètre informative, qui demande juste à l'utilisateur de cliquer pour continuer. Appuyez sur Echap ou fermer cette fenètre lève une exception. *)

let zenity_info s = faire ("zenity --info --window-icon=\".monicone.ico\" --title=\"Projet Puzzle : information\" --text=\""^s^"\"");;
(** Idem. *)

let zenity_notif s = faire ("zenity --notification --window-icon=\".monicone.ico\"--title=\"Projet Puzzle : notification\" --text=\""^s^"\"");;

(** Transforme un char en string. *)

let string_of_char c = let s = "1" in s.[0] <- c; s;;

(** Transforme un char en héxa en sa valeur entière. Accepte aussi bien les minuscules que les majuscules. *)

let int_of_charhex c = match c with
        | '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9' -> int_of_string (string_of_char c);
        | 'A' | 'a' -> 10;
        | 'B' | 'b' -> 11;
        | 'C' | 'c' -> 12;
        | 'D' | 'd' -> 13;
        | 'E' | 'e' -> 14;
        | 'F' | 'f' -> 15;;

(** Transforme un caractère bi hexa (chaine de deux caractères entre 0 et A) en sa valeur. *)

let int_of_hex2 s = 
        let (x,y) = (int_of_charhex s.[0], int_of_charhex s.[1]) in
        x * 16 + y;;

(** Petite fonction outil pour récupérer le code RGB utilisé par Graphics, à partir du rendu utilisé par Zenity dans sa fonction "--color-selection". *)

let parse_color file =
        let s = input_line (open_in file) in
        let (s1, s2, s3) = ( (Resolution.monsub s 1 2), (Resolution.monsub s 5 2), (Resolution.monsub s 9 2)) in
        Graphics.rgb (int_of_hex2 s1) (int_of_hex2 s2) (int_of_hex2 s3);;

(** Petite fonction outil pour extraire un entier du fichier texte se trouvant à l'adresse "file". Cela suppose bien sur que le dit fichier ne contiennent qu'un entier. *)

let parse_int file = int_of_string (input_line (open_in file)) ;;

(** Utilise zenity et les fonctions précédentes pour demander à l'utilisateur de choisir une couleur, en ouvrant une roue des couleurs RGB, l'enregistre dans un fichier temporaire, l'extraie via la fonction "parse_color", et la renvoie. *)

let ask_couleur () =
        zenity_info "Veuillez choisir une couleur";
(*        faire "zenity --color-selection --window-icon=\".monicone.ico\" --title=\"Projet Puzzle : choisissez une couleur\" > .color_choose_zenity.color";*)
                
                (** La valeur de la couleur est stockée dans un fichier temporaire caché. *)

        parse_color ".color_choose_zenity.color";
        Graphics.red;;
(*#121234345656 -> Resolution.couleur *)

(** Utilise zenity et les fonctions précédentes pour demander à l'utilisateur de choisir un entier, en ouvrant un curseur, l'enregistre dans un fichier temporaire, et la renvoie. Comme cette fonction sera utilisée pour demander le nombre de colonnes, lignes et couleurs, on limite les valeurs qu'il est possible de choisir à etre entières, entre 1 et 16. *)

let ask_int () =
(*        zenity_info "Veuillez choisir un entier";*)
        faire "zenity --scale --window-icon=\".monicone.ico\" --text=\"Projet Puzzle : choisissez un entier\" --value=2 --min-value=1 --max-value=16 > .int_choose_zenity.int";
                
                (** La valeur de l'entier est stockée dans un fichier temporaire caché. *)

        parse_int ".int_choose_zenity.int";;

(** La meme mais avec un message customisable. *)

let ask_int_msg s () =
(*        zenity_info "Veuillez choisir un entier";*)
        faire ("zenity --scale --window-icon=\".monicone.ico\" --title=\"Projet puzzle : choisissez une valeur\" --text=\""^s^"\" --value=2 --min-value=1 --max-value=16 > .int_choose_zenity.int");
        parse_int ".int_choose_zenity.int";;

(** Une échelle adaptée selon le nombre de couleurs. Si c est petit, on prend du gris dégradé, sinon on prend un patchwork. *)

let echelle_intelligente = function
        |nb when (nb <= 5) -> (echelle_gris nb)
        |nb -> (genere_echelle_couleur ~cmax:(Graphics.rgb 255 176 0) ~cmin:(Graphics.rgb 0 204 255) ~nb:(nb+1))
;;

(** Utilise 'ask_couleur' pour demander à l'utilisateur de choisir une échelle de couleur. *)

let ask_echelle_couleur nb () =
        try (zenity_info "
Pour colorer les pièces, le systeme utilise une echelle de couleur.
Vous avez choisi de la configurer vous meme : veuillez choisir deux couleurs pour creer cette echelle.
        <i>L'echelle generee est une simple interpolation entre les codes RGB des deux couleurs rentrees.
        Veuillez donc choisir des couleurs bien differentes pour distinguer quelque chose sur l'affichage !</i>"
;
        let x = genere_echelle_couleur ~cmax:(ask_couleur ()) ~cmin:(ask_couleur ()) ~nb:(nb+1) in
        zenity_info "L'echelle de couleur a bien ete enregistree et sera utilisee tant que le programme ne sera pas quitte.";
(*        x) with (Error_Annule(i)) -> (echelle_gris nb);;*)
        x) with (Error_Annule(i)) -> (echelle_intelligente nb);;
        
        (** Par défaut, si l'utilisateur refuse de choisir deux couleurs, on renvoit un échelle de gris adaptée au nombre de couleurs différentes voulues. *)


(*----------------------------------------------------------------------------------------------------------------------------------------------*)

(** Pour transformer ce 'Graphics.color array' en une fonction : couleur -> Graphics.color. Il faut supposer que l'échelle comporte le bon nombre de couleurs. Sinon le résultat produit n'est pas correct (ou en tout cas, on ne peux pas assurer qu'il le soit). *)

let bijection_couleur_color (echelle:(Graphics.color array)) (ma_couleur:Resolution.couleur) =
        
        (** couleur : est un entier entre 1 et c. *)

        echelle.(ma_couleur - 1);;
        
(** Pour remplir un polygone d'une certaine couleur, tout en sauvegardant la couleur courante et le point courant. *)

let fill_custom (ma_couleur:Resolution.couleur) (mes_points:(int * int) array) () =
        let back_svg = Graphics.background in
        let pt_svg = Graphics.current_point () in
        Graphics.set_color ma_couleur;
        Graphics.fill_poly mes_points;
        Graphics.set_color back_svg;
        Graphics.moveto (fst pt_svg) (snd pt_svg);;

(** Pour dessiner une ligne d'une couleur donnée, tout en sauvegardant la couleur courante et le point courant. *)

let line_custom (ma_couleur:Resolution.couleur) (x1:int) (y1:int) (x2:int) (y2:int) () =
        let back_svg = Graphics.background in
        let pt_svg = Graphics.current_point () in
        Graphics.set_color ma_couleur;
        Graphics.moveto x1 y1;
        Graphics.lineto x2 y2;
        Graphics.set_color back_svg;
        Graphics.moveto (fst pt_svg) (snd pt_svg);;

(** Echelle exemple : les exemples seront fait avec 4 couleurs, alors autant avoir une échelle de couleur qui pète un peu pour les exemples ! *)

let rose = 16737946 and mandarine = 16737792 and orange = 16724736 and jaunefaible = 16763904 and saumon = 16750950 and violet = 13369599 and indigo = 6684927 and turquoise = 65484 and vertjaune = 10092288;;

(** Echelle exemple jolie pour moins de 14 couleurs differentes. *)

let fbij_jolie_quatorze ~(c : int) (i:Resolution.couleur) = match i with
        | 1 -> Graphics.red;
        | 2 -> Graphics.green;
        | 3 -> Graphics.yellow;
        | 4 -> Graphics.blue;
        | 0  -> Graphics.black; (* Pour dessiner les lignes *)
        | -1 -> Graphics.white; (* Pour le fond *)
        | 5 -> Graphics.magenta;
        | 6 -> Graphics.cyan;
        | 7 -> rose;
        | 8 -> mandarine;
        | 9 -> orange;
        | 10 -> jaunefaible;
        | 11 -> saumon;
        | 12 -> violet;
        | 13 -> indigo;
        | 14 -> vertjaune;;

(** C'est celle là qu'on change selon le choix de l'utilisateur. *)

let fbij_jolie_plus ~(c : int) = bijection_couleur_color (echelle_jaune c);;

(** L'échelle de couleur utilisée par défaut dans le fichier. *)

let echelle_globale_fichier_initialisee_une_fois = ref (echelle_gris 14);;

(** Pour changer la valeur de l'échelle de couleur globale utilisée dans l'interface. Utilise "Zenity" et 'ask_echelle_couleur' décrit plus haut. *)

let change_echelle_globale nb () =
        echelle_globale_fichier_initialisee_une_fois := (ask_echelle_couleur nb ());;


(** Pour se débrouiller seul : une fonction qui choisit l'échelle pertinente en fonction du nombre de couleurs. En fait, elle convertie seulement l'échelle globale en une fonction qui prend un entier (ie : une des informations associées à un coin de la pièce) et la convertie en Graphics.color. *)

let choisi_echelle_pertinente c =
        bijection_couleur_color (!echelle_globale_fichier_initialisee_une_fois);;
(*        if (c < 15) then (fbij_jolie_quatorze ~c:c)
        else (fbij_jolie_plus ~c:c);;*)


(** On va créer une fonction qui prendre une échelle de taille, une pièce, et affichera la pièce dans la fenêtre Graphics ouverte. Attention, suppose qu'on à une fenêtre ouverte (sinon, 'fatal error' !). *)

(** L'argument 's' correspond à la taille en pixel de la moitié de la pièce, et est optionnel. *)

(** L'argument 'c' correspond au nombre de couleurs. *)

(** Les arguments 'x' et 'y' correspondent aux coordonnées du coin inférieur gauche de la pièce dessinée. *)

let afficher_piece ?(x = 0) ?(y = 0) ~(p:Resolution.piece) ?(s = 50) ~(c:int) =
                
                (** D'abord récupérer les couleurs qu'on dessinera. *)

        let back_svg = Graphics.background in
        let pt_svg = Graphics.current_point () in
        let (b,g,h,d) = (p.b, p.g, p.h, p.d) in
        
        (** Ces 4 valeurs sont des entiers. *)

        let fbij = (choisi_echelle_pertinente c) in
(*        let fbij = bijection_couleur_color (echelle_gris c) in*)
(*        let fbij = bijection_couleur_color (echelle_rouge c) in*)
(*        let fbij = bijection_couleur_color (echelle_vert c) in*)
(*        let fbij = bijection_couleur_color (echelle_bleu c) in*)
(*        let fbij = bijection_couleur_color (echelle_jaune c) in*)
(*        let fbij = bijection_couleur_color (echelle_metal c) in*)
(*        let fbij = bijection_couleur_color (echelle_violet c) in*)
(*        let fbij = fbij_jolie_quatorze ~c:c in*)
        
        (** Bijection : Resolution.couleur -> Graphics.color. *)

        let (bc,gc,hc,dc) = ((fbij b), (fbij g), (fbij h), (fbij d)) in
        
        (** Les 4 couleurs de notre pièce. *)

        Graphics.set_color couleur_lignes;
                
                (** Ensuite dessiner le rectangle. *)

        draw_rect x y (2*s) (2*s);
        
                
                (** Ensuite remplir les 4 triangles. *)

        (* Celle du bas *)
        fill_custom bc [| (x, y); (x+2*s, y); (x+s, y+s)|] ();
        (* Celle de gauche *)
        fill_custom gc [| (x, y); (x, y+2*s); (x+s, y+s)|] ();
        (* Celle du haut *)
        fill_custom hc [| (x+s, y+s); (x, y+2*s); (x+2*s, y+2*s)|] ();
        (* Celle de droite *)
        fill_custom dc [| (x+s, y+s); (x+2*s, y); (x+2*s, y+2*s)|] ();
                
                (** Et il faut enfin écrire les nombres. *)

        Graphics.set_text_size 8;
        Graphics.set_color couleur_lignes;
        (* Celle du bas *)
        draw_int ~x:(x + s) ~y:(y + 1*s/5) ~i:b;
        Graphics.set_color couleur_lignes;
        (* Celle de gauche *)
        draw_int ~x:(x + 1*s/5) ~y:(y + s) ~i:g;
        Graphics.set_color couleur_lignes;
        (* Celle du haut *)
        draw_int ~x:(x + s) ~y:(y + s + 3*s/5) ~i:h;
        Graphics.set_color couleur_lignes;
        (* Celle de droite *)
        draw_int ~x:(x + s + 3*s/5) ~y:(y + s) ~i:d;
                
                (** Ensuite dessiner les diagonales. *)

        Graphics.set_color couleur_lignes;
        (* La première '/' *)
        Graphics.moveto x y;
        Graphics.lineto (x + 2*s) (y + 2*s);
(*        line_custom couleur_lignes x y (x + 2*s) (y + 2*s);*)
        (* La seconde '\' *)
        Graphics.moveto x (y + 2*s);
        Graphics.lineto (x + 2*s) y;
(*        line_custom couleur_lignes x (y + 2*s) (x + 2*s) y;*)
                
                (** On rétablit l'état courant. *)

        Graphics.set_color back_svg;
        Graphics.moveto (fst pt_svg) (snd pt_svg);;

(** Celle-là est un simple raffinement de la précédente, elle ne fait rien si on lui donne un 'None' (je rappelle qu'un None dans une case du plateau signifie que rien n'a encore été fixé sur cette case là). *)

let afficher_piece_option ?(x = 0) ?(y = 0) ~(p:Resolution.piece option) ?(s = 50) ~(c:int) =
        match p with
        | None -> ();
        | Some pp -> afficher_piece ~x:x ~y:y ~p:pp ~s:s ~c:c;;

(*--------------------------------------------------------------------------------------------------------------------------------------------- *)
(*!*)(* ESSAIs *)

(*init_fenetre ~x:800 ~y:600 ();;*)

let c = 14;;
let p0 = Resolution.piece_random ~c:c ();;

(*afficher_piece ~x:100 ~y:100 ~p:p0 ~s:60 ~c:c;;*)

(** Une petite fonction test, qui dessine une pièce aléatoire au point courant. *)

let new_piece c =
        let p0 = Resolution.piece_random ~c:c () in
        afficher_piece ~x:(Graphics.current_x ()) ~y:(Graphics.current_y ()) ~p:p0 ~s:40 ~c:c;;


                
                (** *)

                
                (** Squelette du programme d'interface. *)

                
                (** *)


(** Si une action fait arréter l'interface. *)

exception Fin;;

(** Générateur d'interface. Librement inspiré du livre 'Développement d'applications en Ocaml'. *)

let squel ~f_init ~f_end ~f_key ~f_mouse ~f_except = 
        f_init () ;
        try 
        while true do 
                try 
                        let s = Graphics.wait_next_event  [Graphics.Button_downGraphics.Key_pressed
                        in 
                        if s.Graphics.keypressed then f_key s.Graphics.key
                        else 
                         if s.Graphics.button 
                         then f_mouse s.Graphics.mouse_x s.Graphics.mouse_y
                with 
                | Fin -> raise Fin
                |  e  -> f_except e
        done
        with 
        Fin  -> f_end () ;;

(** *)

(** Un essai d'utilisation de ce squelette d'interface. *)


(** On fait une première interface simpliste, qui dessine une pièce au point courant dès qu'on tape sur 'Espace'. *)
        
let trait_char cou c = match c with
        | '\027'  -> raise Fin
        | '\n' -> clear_graph ()
        | ' '  -> new_piece cou
        |   _  -> Graphics.set_color couleur_lignes; Graphics.draw_char c ;;
        
(*let trait_char c = new_piece ();;*)

(** Ce premier exemple était satisfaisant. *)

let exemples_piece_aleatoire c = squel 
        ~f_init:(fun () -> 
                init_fenetre ~x:800 ~y:600 ();
                Graphics.moveto 100 100; )
        ~f_end:(fun () -> Graphics.clear_graph(); print_string "\nJ'espère que ca vous a fait rire :D !"Graphics.close_graph ();)
                
                (** Pour fermer, on efface le graphe, affiche un message, et quitte. *)

        ~f_key:(trait_char c)
                
                (** On utilise la fonction précédente de traitement du caractère pressé.*)

        ~f_mouse:(fun x y -> Graphics.moveto x y)
                
                (** On actualise le point courant au point cliqué. *)

        ~f_except:(fun e -> ()) ;;

(*!*)(* ESSAIS *)
(*print_string "Essai : lancement";*)
(*print_string "Tapez :\n\tespace pour une nouvelle pièce,\n\tenter pour effacer,\n\tescape pour quitter,\nCliquez pour définir le point où serons tracés les pièces.\n\tProgramme par Lilian BESSON.";*)
(*exemples_piece_aleatoire 15;;*)
(*Graphics.close_graph ();;*)


(*--------------------------------------------------------------------------------------------------------------------------------------------- *)
(** Deux exceptions si on veut tracer un plateau trop grand pour la taille de fenètre qu'on peut utiliser. Je ne sais pas encore comment faire une fenètre qui défile en OCaml avec le module Graphics. *)

exception Erreur_trop_grand_h;;
exception Erreur_trop_grand_v;;

(** Paramètres GLOBAUX des tracés. La résolution d'écran correspondante doit être supérieure a 1000x700 : 1024*768 ou mieux. Malgré cela, normalement tout marche sur des résolutions plus petite, seulement la détection d'une demande de tracés impossible ne sera pas faite. Si vous avez un écran plus petit, ne demandez pas trop de pièces ;) ! *)

let resolution_x = 1000 and resolution_y = 700 and taille_piece = 30;;

(** Pour afficher un plateau. *)

let afficher_plateau (mon_plateau : Resolution.plateau ) =
        let (c, n, m, p) = (mon_plateau.c, mon_plateau.n, mon_plateau.m, mon_plateau.p) in
        
        (** p est une matrice de pièce *)

(* On construit le quadrillage : fonction bijective (i,j) -> (x_i, y_j) qui indique les points ou tracer les pièces du plateau. *)
        let s = taille_piece in 
             (** Largeur d'une pièce. Attention, cela implique qu'on ne sait pas tracer des plateaux trop gros. On va créer une fenètre 1000x700, donc on ne peut afficher que 14 x 14 pièces. *)

        if (m > (resolution_x / (2*taille_piece  - 1))) then raise Erreur_trop_grand_h;
        if (n > (resolution_y / (2*taille_piece  - 1))) then raise Erreur_trop_grand_v;
        let quadrillage ~i ~j =
                ((1 + (2*s+4)*i ),(1 + (2*s+4)*j ));
        in
        
        (** On initialise la fenètre. *)

        init_fenetre ~x:resolution_x ~y:resolution_y ();
        Graphics.moveto 0 0;
        
        (** Puis on trace chaque pièce. *)

        for i = 0 to (m-1) do
                for j = 0 to (n-1) do
                        afficher_piece_option ~x:(10 + (2*s+4)*i ) ~y:(10 + (2*s+4)*j ) ~p:(p.(i).(j)) ~s:s ~c:c;
                done;
        done;
        
        (** On pourrait raffiner en écrivant quelques infos dans le titre de la fenètre.. a voir ! *)

        ();;

(** Un petit test pour vérifier cette fonction. *)

let test_random ~m ~n ~c ~nb =
        for k = 1 to nb do
                afficher_plateau (Resolution.plateau_of_instance (Resolution.creer_instance_random ~m:m ~n:n ~c:c ()));
                print_string "\nVoila un exemple d'instance aléatoire.\nContinuons-nous ?";
                read_line ();
        done;
        Graphics.close_graph();;

(*test_random ~c:14 ~n:4 ~m:4 ~nb:1;;*)

let actualise_taille ~s ~n ~m () =
        let new_taille_x = 20 + (2*s+4)*m in
        let new_taille_y = 20 + (2*s+4)*n in
        Graphics.resize_window new_taille_x new_taille_y;;

if (!(Sys.interactive)) then ex();;
(*--------------------------------------------------------------------------------------------------------------------------------------------- *)
(** Pour lire un plateau dans un fichier, et l'afficher, directement. *)

let lit_et_affiche_plateau ~file =
        let plateau_local = Resolution.lecture_plateau ~file:file in
        afficher_plateau plateau_local;
        Graphics.set_window_title ("Projet Puzzle : plateau dans le fichier \""^(file)^"\"");;


(** Pour afficher une instance avant qu'elle ne soit résolue. *)

let afficher_instance mon_instance =
                
                (** La conversion instance -> plateau ici utilisée N'EST PAS LA RESOLUTION, mais seulement une conversion naïve, ou on transforme la liste de pièces de l'instance, en un tableau de pièce du plateau. *)

        afficher_plateau (Resolution.plateau_of_instance mon_instance);;


(** Pour lire une instance dans un fichier, et l'afficher, directement. *)

let lit_et_affiche_instance ~file =
        let instance_locale = lecture_instance ~file:file in
        afficher_instance instance_locale;
        Graphics.set_window_title ("Projet Puzzle : instance dans le fichier \""^(file)^"\"");;

(** Pour générer et afficher une instance aléatoire. *)

let creer_instance_random_et_affiche ~m ~n ~c =
                
                (** Cette fonction là est utilisé chaque fois que l'utilisateur presse le bouton 'Espace' dans l'interface graphique. *)

        let instance_renvoyee = (Resolution.creer_instance_random ~m:m ~n:n ~c:c ()) in
        afficher_instance instance_renvoyee;
        Graphics.set_window_title ("Projet Puzzle : instance aleatoire m="^(string_of_int m)^", n="^(string_of_int n)^", c="^(string_of_int c));
        print_string ("\nVoila un exemple d'instance aléatoire, avec les paramètres suivants : m="^(string_of_int m)^", n="^(string_of_int n)^", c="^(string_of_int c));
(*        Graphics.close_graph();*)
        instance_renvoyee;;

(** Un alias, plus court. *)

let aff_random = creer_instance_random_et_affiche;;

(*aff_random ~m:6 ~n:6 ~c:6;;*)

(*--------------------------------------------------------------------------------------------------------------------------------------------- *)
(** Pour afficher une instance, demander confirmation avant de lancer la résolution, puis résoudre en mode interactif (affiche le plateau courant a chaque étape, et affiche des infos log sur le déroulement des opérations), on va utiliser ce qui suit. *)


(** Pour etre utilisee avec les fonctions de la librairie 'resolution.ml'. Permet d'afficher en couleur un plateau, puis de demander confirmation à l'utilisateur avant de continuer. Attention : a besoin d'une fenètre DEJA ouverte ! *)

let printP mon_plateau =
        afficher_plateau mon_plateau;
        Graphics.set_window_title "Projet Puzzle : resolution EN COURS ...";
(*        zenity_info ("Le plateau a été affiché.\n\tVoulez-vous continuer ?");;*)
        print_string ("\nLe plateau courant a été affiché.");;

(** La meme en affichant le temps du chronomètre. *)

let printP_chrono mon_plateau =
        printP mon_plateau;
        Graphics.set_window_title ("Projet Puzzle : resolution en cours depuis "^(Temps.temps_chrono ()));
        print_string ("\n  Temps courant : "^(Temps.temps_chrono ()));;
        
(** La meme en affichant le temps du chronomètre, mais sans afficher le plateau. *)

let printP_chrono_no mon_plateau =
        Graphics.set_window_title ("Projet Puzzle : resolution en cours depuis "^(Temps.temps_chrono ()));
        print_string ("\n  Temps courant : "^(Temps.temps_chrono ()));;

(** Utilise la résolution naïve décrite dans 'resolution.ml' mais avec l'affichage à l'écran, en couleur, intermédiaire des plateaux testés. *)

let resoud_naivement_graphique_nochrono = Resolution.resoud_naivement ~printP:printP;;

(** Avec chronometre. Attention : suppose que le chronometre du module 'Temps' a déja était lancé via l'appel à 'Temps.lancer_chrono ()'. *)

let resoud_naivement_graphique = Resolution.resoud_naivement ~printP:printP_chrono;;

(** Avec chronometre. Attention : suppose que le chronometre du module 'Temps' a déja était lancé via l'appel à 'Temps.lancer_chrono ()'. *)

let resoud_naivement_no_graphique = Resolution.resoud_naivement ~printP:printP_chrono_no;;


(*--------------------------------------------------------------------------------------------------------------------------------------------- *)
(** Le contenu de l'aide de l'interface graphique. *)

let aide_interface = "
<i>Projet Puzzle - ENS de Cachan 2012
Programme par Lilian BESSON (c) et (r).</i>

<small>Ce programme est écrit en Ocaml, et utilise le module graphique de la distribution standard Graphics pour l'affichage, et l'outil Zenity, disponible gratuitement sous Linux </small>(''$ sudo apt-get install zenity'' pour le récupérer).

Durant la boucle intéractive, les touches suivantes déclenchent les actions suivantes :
        <b>h</b>        :        affiche ce message d'aide,
        <b>'enter'</b> :        efface la fenêtre,
                <b>'e'</b>        fait la meme chose (le code de la touche 'enter' varie selon le mode d'exécution).
        <b>u</b>        :        change le nombre de couleurs utilisées,
        <b>c</b>        :        change l'échelle de couleur utilisée,
        <b>m</b>        :        change le nombre de colonnes des instances générées,
        <b>n</b>        :        change le nombre de lignes   des instances générées,
        <b>'Escape'</b>:        quitte proprement et enregistre l'instance / le plateau courant dans un fichier de sauvegarde caché,
        <b>i</b>        :        sauvegarde l'image de la fenêtre dans une image JPEG externe, en demandant un nom de fichier (demande confirmation avant écrasement). Utilise l'outil 'import', disponible gratuitement sous Linux (''$ sudo apt-get install imagemagick'' pour le récupérer),
        <b>s</b>        :        sauvegarde l'instance / le plateau courant affiché à l'ecran dans un fichier texte externe, en demandant un nom de fichier (demande confirmation avant écrasement).
        <b>b</b>        :        ouvre une instance déjà sauvegardée, en utilisant le navigateur de document de Zenity.
        <b>'space'</b> :        génére une nouvelle instance aléatoire avec les paramètres courant, et l'affiche,
        <b>w</b>        :        génére une nouvelle instance aléatoire, <i>ayant</i> une solution, avec les paramètres courant, et l'affiche,
        <b>y</b>         :        génére une <i>solution</i> aleatoire, avec les paramètres courant, et l'affiche,
        <b>p</b>        :        demande de choisir une pièce, puis une position pour cette pièce, et enfin confirmation pour la placer dans l'instance courante. Redessine celle ci ensuite. <i>Un clic de la souris sur une position de pièce fait la même chose.</i>
        <b>v</b>        :        pour vérifier la validité du plateau dessiné à l'écran, et afficher le résultat (valide, ou une preuve de non validité) dans une fenêtre informative,
        <b>r</b>        :        <b>résoud</b> l'instance courante affichée à l'écran, en affichant les plateaux intermédiaires,
        <b>k</b>        :        <b>résoud</b> l'instance courante affichée à l'écran, sans rien afficher (plus rapide).

Appuyez sur Echap pour fermer cette fenêtre d'aide et retourner au programme. Evitez de quitter brutalement, la gestion souple d'une 'fatal error' n'est pas encore au point (ceci est valable pour tout le programme).
"
;;


(** Pour afficher cette aide dans une fenètre. *)

let aide_ic () =
        print_string "Ouverture de l'aide ...";
        faire ("zenity --info --window-icon=\".monicone.ico\" --title=\"Projet puzzle : aide\" --text=\""^aide_interface^"\" --width=1000 --height=600 ");;
        
(** Trois alias, pour demander facilement de nouvelles valeurs pour un des trois paramètres de l'interface : le nombre de lignes, de colonnes ou de couleurs. Ces trois fonctions de demandent de valeurs utilisent "Zenity". *)

let new_n_ic () = ask_int_msg "Veuillez choisir le nombre de lignes" ()
and new_m_ic () = ask_int_msg "Veuillez choisir le nombre de colonnes" ()
and new_c_ic () = ask_int_msg "Veuillez choisir le nombre de couleurs" ();;

(** Les deux fonctions suivantes sont pour demander à l'utilisateur un fichier dans un répertoire, pour charger une instance enregistrée auparavant. *)


        
        (** Renvoie le contenu de la première ligne d'un fichier texte. *)

let parse_fichier file =        (* fichiers a une seule lignes *)
        input_line (open_in file);;

        
        (** Et j'utilise cela pour avoir une fonction intéractive de demande de fichier. *)
 
let ask_fichier () =
        let dir = Sys.getcwd () in
        faire ("zenity --file-selection --window-icon=\".monicone.ico\" --title=\"Projet puzzle : ouvrir une instance\" --confirm-overwrite --filename=\""^dir^"/\" > .fichier_choisi.adresse");
                
                (** On se place dans le répertoire courant par défaut. *)

        parse_fichier ".fichier_choisi.adresse";;

        
        (** Pour réaliser une capture d'écran, le système invite l'utilisateur à choisir un nom pour sa capture, ce qui est réalisé avec cette fonction là. *)

let ask_value msg () =
        (faire ("zenity --entry --window-icon=\".monicone.ico\" --title=\"Projet Puzzle\" --text=\""^msg^"\" > .value_choisie.value"));
        parse_fichier ".value_choisie.value";;

        
        (** Condense deux actions, pour permettre de directement renvoyé l'instance choisie dans le répertoire. *)

let browse_instance () =
(*        zenity_info "Ouvrir une instance :";*)
        lecture_instance (ask_fichier ());;

        
        (** Pour demander confirmation avant d'écraser un fichier. De façon graphique, et dans le terminal affiche aussi un message (mais n'attend pas de réponse !). *)

let demande_confirmation_graphique file =
        print_string ("Projet Puzzle - ecriture_instance : le fichier '"^file^"' existe deja. Voulez-vous l'ecraser ? [O/n] (cocher 'Valider' dans la fenètre Zenity pour oui, ou 'Annuler' pour non)\n");
        zenity_info ("Projet Puzzle - ecriture_instance : le fichier '"^file^"' existe deja. Voulez-vous l'ecraser ? [O/n]\n");;


let ecrit_instance_browse_2 mon_instance () = 
        zenity_info "Donnez un nom de fichier ou ecrire l'instance courante (au format décrit en annexe):";
        Resolution.ecriture_instance ~action_confirmation:demande_confirmation_graphique ~file:((ask_fichier ())) ~i:mon_instance;;

(** Pour sauvegarder l'instance (ou le plateau) affiché à l'écran, on utilise cette fonction là. *)

let ecrit_instance_browse mon_instance () = 
(*        zenity_info "Donnez un nom de fichier ou ecrire l'instance courante (au format décrit en annexe):";*)
        Resolution.ecriture_instance ~action_confirmation:demande_confirmation_graphique ~file:(ask_value "Donnez un nom de fichier ou ecrire l'instance courante (au format décrit en annexe):" ()) ~i:mon_instance;;

(** Pour réaliser une capture d'écran. On demande d'abord un nom de fichier, puis on utilise l'outil "Import", gratuitement disponible sous Linux, pour capturer le contenu de la fenêtre ayant pour titre "Projet Puzzle". *)

let capture_ecran () =
        Graphics.set_window_title "Projet Puzzle";
(*        zenity_info "Choisir un fichier ou ecrire la capture d'ecran :";*)
        let image_direction = (ask_value "Donnez un nom de fichier ou ecrire la capture d'ecran (au format .jpg):" ()) in
        faire ("import -silent -window \"Projet Puzzle\" "^image_direction);;
                
                (** Attention, si deux fenêtres sont ouvertes avec le même nom, la 'sémantique' de la réaction de l'outil Import n'est pas définie. Et il y aura très certainement une erreur. *)


(** Macro pour actualiser rapidement le titre de la fenêtre en fonction des trois paramètres locaux n, m, et c. *)

let titre_ic ~c ~n ~m =
        Graphics.set_window_title ("Projet Puzzle : m="^(string_of_int m)^", n="^(string_of_int n)^", c="^(string_of_int c));;

(** Si la recherche d'une solution à l'instance argument a échouée, affiche un message d'erreur, et actualise le titre de la fenêtre en fonction de cela. *)

let fail_recherche mon_instance () =
        Graphics.set_window_title "Projet Puzzle : resolution ECHOUEE (aucune solution n'a ete trouvee)";
        zenity_info "<b>Aucune solution n'a ete trouvee</b> a l'instance suivante qui a ete reaffichee dans la fenetre graphique";
        afficher_instance mon_instance;
        Graphics.set_window_title "Projet Puzzle : resolution ECHOUEE (aucune solution n'a ete trouvee)";;


(*--------------------------------------------------------------------------------------------------------------------------------------------- *)
(** Pour passez dans le mode ou l'utilisateur peut choisir une pièce, en rentrant ses informations, et la fixer sur une des cases du plateau, avant de lancer la complétion aléatoire par exemple. *)


(** Pour demander une pièce a l'utilisateur. Demande les paramètres locaux n, m, c. *)

let ask_piece ~c () =
(*        ask_int_msg "Veuillez choisir le nombre de lignes" ()*)
        let ask_int_msgc s = 
                faire ("zenity --scale --window-icon=\".monicone.ico\" --title=\"Projet puzzle : choix d'une pièce\" --text=\""^s^"\" --value=1 --min-value=1 --max-value="^(string_of_int c)^" > .int_choose_zenity.int");
                parse_int ".int_choose_zenity.int";
        in
        { b = (ask_int_msgc "Couleur du bas ?"); g = (ask_int_msgc "Couleur de gauche ?"); h = (ask_int_msgc "Couleur du haut ?"); d = (ask_int_msgc "Couleur de droite ?") };;

(** Pour choisir la position de la pièce, à la souris. Demande aussi 's', le paramètre d'échelle utilisé pour la taille des pièces, afin de pouvoir retrouver les indices (i,j) correspondant à la zone cliquée. *)

let ask_position ~n ~m ~s () =
        
        (** s doit valoir "taille_piece". *)

        Graphics.set_window_title "Projet Puzzle : ou placer cette piece ? (Cliquer sur une case du plateau, ou 'Echap' pour annuler)";
        zenity_info "Projet Puzzle : où placer cette pièce ? (Cliquer sur une case du plateau, ou 'Echap' pour annuler)";
        
        (** Pour que l'utilisateur choisisse un point à l'écran. *)

        let (x_loc, y_loc) = (ref 0, ref 0) in
        let f_key = function
                |'\027' -> raise Fin;
                | _        -> ();
        in
        let f_mouse x y = 
                x_loc := x;        y_loc := y;
                Graphics.moveto x y;
                raise Fin;
        in
        (try 
        while true do 
                try 
                        let s = Graphics.wait_next_event  [Graphics.Button_downGraphics.Key_pressed
                        in 
                        if s.Graphics.keypressed then f_key s.Graphics.key
                        else 
                         if s.Graphics.button 
                         then f_mouse s.Graphics.mouse_x s.Graphics.mouse_y
                with 
                | Fin -> raise Fin
        done
        with Fin  -> () ;
        );
        print_string ("Vous avez cliqué en x="^(string_of_int !x_loc)^", et y="^(string_of_int !y_loc)^". Cela vous conviend ?");
        
        (** Pour retrouver les indices (i,j) correspondant. *)

(*~x:(10 + (2*s+4)*i ) ~y:(10 + (2*s+4)*j )*)
        let (i, j) = ( ((!x_loc - 10) / (2*s+4)), ((!y_loc - 10) / (2*s+4))) in
        if ((i < 0) || (i >= m)) then (
                zenity_info "Projet Puzzle : où placer cette pièce ? La coordonnée horizontale n'est pas valide";
                raise Fin;
        );
        if ((j < 0) || (j >= n)) then (
                zenity_info "Projet Puzzle : où placer cette pièce ? La coordonnée verticale n'est pas valide";
                raise Fin;
        );
        (i, j);;
        
        




(*~s:taille_piece*)

(*--------------------------------------------------------------------------------------------------------------------------------------------- *)
(** Et enfin l'interface complète, qui utilise le squelette d'interface présenté plus haut, et toutes les fonctions précédentes. *)

let interface_complete () = 
        let c_ici = ref 4 in
        let n_ici = ref 2 in
        let m_ici = ref 2 in
                
                (** On utilise trois paramètres courant pour n, m, et c. *)

        let instance_ici = ref (Resolution.creer_instance_random ~m:!m_ici ~n:!n_ici ~c:!c_ici ()) in
        let plateau_ici = ref (Resolution.plateau_of_instance !instance_ici) in
                
                (** Et deux paramètres courant pour l'instance locale et le plateau local. *)

                let trait_char c = (match c with
                        
                        (** Pour afficher l'aide. *)

                        | 'h'  -> aide_ic ()
                        
                        (** Pour quitter. *)

                        | '\027' -> raise Fin
                        
                        (** Pour effacer l'écran. *)

                        | '\n' | 'e'-> Graphics.clear_graph ()
                        
                        (** Pour changer le nombre de couleurs. *)

                        | 'u'  -> (c_ici := new_c_ic ();
                                        change_echelle_globale !c_ici ();
                                        actualise_taille ~s:taille_piece ~n:!n_ici ~m:!m_ici ();
                                        titre_ic ~c:!c_ici ~n:!n_ici ~m:!m_ici;)
(*                        (** Pour changer l'échelle de couleur. *)*)
(*                        | 'c'  -> (change_echelle_globale !c_ici ();*)
(*                                        actualise_taille ~s:taille_piece ~n:!n_ici ~m:!m_ici ();*)
(*                                        afficher_instance !instance_ici;*)
(*                                        titre_ic ~c:!c_ici ~n:!n_ici ~m:!m_ici;)*)
                        
                        (** Pour changer n. *)

                        | 'n'  -> (n_ici := new_n_ic ();
                                        actualise_taille ~s:taille_piece ~n:!n_ici ~m:!m_ici ();
                                        titre_ic ~c:!c_ici ~n:!n_ici ~m:!m_ici;)
                        
                        (** Pour changer m. *)

                        | 'm'  -> (m_ici := new_m_ic ();
                                        actualise_taille ~s:taille_piece ~n:!n_ici ~m:!m_ici ();
                                        titre_ic ~c:!c_ici ~n:!n_ici ~m:!m_ici;)
                        
                        (** Pour charger une instance enregistrée dans un fichier. Actualise les paramètres courants (c, n, m). *)

                        | 'b'  -> (instance_ici := (browse_instance ());
                                        c_ici := !instance_ici.ci;
                                        change_echelle_globale !c_ici ();
                                        n_ici := !instance_ici.ni;
                                        m_ici := !instance_ici.mi;
                                        actualise_taille ~s:taille_piece ~n:!n_ici ~m:!m_ici ();
                                        afficher_instance !instance_ici;
                                        titre_ic ~c:!c_ici ~n:!n_ici ~m:!m_ici;)
                        
                        (** Pour choisir une pièce, sa position, changer l'instance courante, et la réafficher. *)

                        | 'p'  -> (
                                  let piece_courante = (ask_piece ~c:!c_ici ()) in
                                  let (i0, j0) = (ask_position ~n:!n_ici ~m:!m_ici ~s:taille_piece ()) in
                                  let p = (Resolution.matrix_of_list ~n:!n_ici ~m:!m_ici ~l:(!instance_ici.pi)) in
                                  p.(i0).(j0) <- piece_courante;
                                  instance_ici := { ci = !c_ici; ni = !n_ici; mi = !m_ici; pi = (Resolution.list_of_matrix p)};
                                  actualise_taille ~s:taille_piece ~n:!n_ici ~m:!m_ici ();
                                  afficher_instance !instance_ici;
                                  titre_ic ~c:!c_ici ~n:!n_ici ~m:!m_ici;
                                  )
                        
                        (** Pour vérifier la validité du plateau courant. *)

                        | 'v'  ->(
                                  (try (Resolution.est_valide_plateau (Resolution.plateau_of_instance !instance_ici)) with
                                        | Resolution.Solution_trouvee -> 
                                                zenity_info "Le plateau affiché à l'écran <span color='red'>est valide</span>.";
                                        | Resolution.Mauvais_tableau  -> (* n'arrive jamais. *)
                                                zenity_info "Le plateau affiché à l'écran <span color='red'>n'est pas valide</span> \n<b>car</b> il est mal construit.";
                                        | Resolution.Erreur_compatibilite_h(ii0,jj0)        ->
                                                zenity_info ("Le plateau affiché à l'écran <span color='red'>n'est pas valide</span> \n<b>car</b> il y a une incompatibilité horizontale, entre la case ("^(string_of_int ii0)^","^(string_of_int jj0)^") et ("^(string_of_int (ii0 + 1))^","^(string_of_int jj0)^").");
                                        |Resolution.Erreur_compatibilite_v(ii1,jj1)        -> 
                                                zenity_info ("Le plateau affiché à l'écran <span color='red'>n'est pas valide</span> \n<b>car</b> il y a une incompatibilité verticale, entre la case ("^(string_of_int ii1)^","^(string_of_int jj1)^") et ("^(string_of_int ii1)^","^(string_of_int (jj1 + 1))^").");
                                );
                                  
                                ) 
                        
                        (** Pour résoudre l'instance courante. *)

                        | 'r'  -> (afficher_instance !instance_ici;
                                  zenity_info "La resolution de l'instance courante va commencer. Confirmez-vous ?";
                                  Temps.lancer_chrono ();
                                  Graphics.set_window_title "Projet Puzzle : resolution EN COURS ...";
                                  try (
                                  plateau_ici := resoud_naivement_graphique !instance_ici;
                                  instance_ici := instance_of_plateau !plateau_ici;
                                  afficher_instance !instance_ici;
                                  Graphics.set_window_title "Projet Puzzle : resolution terminee (UNE solution est affichee)";
(*                                  zenity_info "La résolution de l'instance courante est terminée.";*)
(*                                  faire "export date2=`date \"+%Hh:%Mm-%Ss\"`";*)
                                  zenity_info ("La résolution de l'instance courante avec affichage intermédiaire est terminée.\nTemps interne mis = <b><span color='red'>"^(Temps.temps_chrono ())^"</span></b>\nRésolution lancée avec c="^(string_of_int !c_ici)^", n="^(string_of_int !n_ici)^", m="^(string_of_int !m_ici));
(*                                  zenity_info ("La résolution de l'instance courante est terminée.\nTemps interne mis = <b><span color='red'>"^(Temps.temps_chrono ())^"</span></b>\nRésolution lancée à $date1; avec c="^(string_of_int !c_ici)^", n="^(string_of_int !n_ici)^", m="^(string_of_int !m_ici)^";\n  et résolution terminée à $date2");*)
                                  ) with 
                                          | Resolution.Aucune_solution | Resolution.Mauvais_tableau | Resolution.Erreur_compatibilite_h(_,_) | Resolution.Erreur_compatibilite_v(_,_) -> (fail_recherche !instance_ici ());)
                        
                        (** Pour résoudre l'instance courante sans afficher les plateaux courants. *)

                        | 'k'  -> (afficher_instance !instance_ici;
                                  zenity_info "La resolution de l'instance courante sans affichages intermédiaires va commencer. Confirmez-vous ?";
                                  Temps.lancer_chrono ();
                                  Graphics.set_window_title "Projet Puzzle : resolution EN COURS ...";
                                  try (
                                  plateau_ici := resoud_naivement_no_graphique !instance_ici;
                                  instance_ici := instance_of_plateau !plateau_ici;
                                  afficher_instance !instance_ici;
                                  Graphics.set_window_title "Projet Puzzle : resolution terminee (UNE solution est affichee)";
(*                                  zenity_info "La résolution de l'instance courante est terminée.";*)
(*                                  faire "export date2=`date \"+%Hh:%Mm-%Ss\"`";*)
                                  zenity_info ("La résolution de l'instance courante sans affichage intermédiaire est terminée.\nTemps interne mis = <b><span color='red'>"^(Temps.temps_chrono ())^"</span></b>\nRésolution lancée avec c="^(string_of_int !c_ici)^", n="^(string_of_int !n_ici)^", m="^(string_of_int !m_ici));
(*                                  zenity_info ("La résolution de l'instance courante est terminée.\nTemps interne mis = <b><span color='red'>"^(Temps.temps_chrono ())^"</span></b>\nRésolution lancée à $date1; avec c="^(string_of_int !c_ici)^", n="^(string_of_int !n_ici)^", m="^(string_of_int !m_ici)^";\n  et résolution terminée à $date2");*)
                                  ) with 
                                          | Resolution.Aucune_solution | Resolution.Mauvais_tableau | Resolution.Erreur_compatibilite_h(_,_) | Resolution.Erreur_compatibilite_v(_,_) -> (fail_recherche !instance_ici ());)
                        
                        (** Pour sauver l'image courante en un fichier .jpeg. *)

                        | 'i'        -> (capture_ecran ();
(*                                        actualise_taille ~s:taille_piece ~n:!n_ici ~m:!m_ici ();*)
                                        afficher_instance !instance_ici;
                                        titre_ic ~c:!c_ici ~n:!n_ici ~m:!m_ici;)
                        
                        (** Pour sauver l'instance courante en un fichier .instance. *)

                        | 's'        -> (ecrit_instance_browse !instance_ici ();
(*                                        actualise_taille ~s:taille_piece ~n:!n_ici ~m:!m_ici ();*)
                                        afficher_instance !instance_ici;
                                        titre_ic ~c:!c_ici ~n:!n_ici ~m:!m_ici;)
                        
                        (** Pour générer une instance aléatoire avec les paramètres locaux. *)

                        | ' '        -> (
                                        actualise_taille ~s:taille_piece ~n:!n_ici ~m:!m_ici ();
                                        instance_ici := (aff_random ~m:!m_ici ~n:!n_ici ~c:!c_ici);
(*                                        afficher_instance !instance_ici;*)
                                        titre_ic ~c:!c_ici ~n:!n_ici ~m:!m_ici;)
                        
                        (** Pour générer une nouvelle instance aleatoire, ayant une solution, avec les parametres courant, et l'afficher. *)

                        | 'w'        -> ( instance_ici := (Resolution.generer_instance_solution ~n:!n_ici ~m:!m_ici ~c:!c_ici ());
                                        actualise_taille ~s:taille_piece ~n:!n_ici ~m:!m_ici ();
                                        afficher_instance !instance_ici;
                                        titre_ic ~c:!c_ici ~n:!n_ici ~m:!m_ici;)
                        
                        (** Pour générer une solution aleatoire, avec les parametres courant, et l'afficher. *)

                        | 'y'        -> ( plateau_ici := (Resolution.generer_plateau_solution ~n:!n_ici ~m:!m_ici ~c:!c_ici ());
                                        instance_ici := (Resolution.instance_of_plateau !plateau_ici);
                                        actualise_taille ~s:taille_piece ~n:!n_ici ~m:!m_ici ();
                                        afficher_instance !instance_ici;
                                        titre_ic ~c:!c_ici ~n:!n_ici ~m:!m_ici;)
                        |   _  -> ()
                ) in
        (squel 
        ~f_init:(fun () -> 
                (zenity_info "Lancement de l'interface utilisateur du projet Puzzle en cours...";
                init_fenetre ~x:700 ~y:700 ();
                Graphics.open_graph " 700x700";
                Graphics.set_window_title "Projet Puzzle";
                Graphics.moveto 0 0;
                n_ici := new_m_ic ();
                m_ici := new_n_ic ();
                c_ici := new_c_ic ();
                change_echelle_globale !c_ici ();
                actualise_taille ~s:taille_piece ~n:!n_ici ~m:!m_ici ();
                titre_ic ~c:!c_ici ~n:!n_ici ~m:!m_ici;
                instance_ici := (Resolution.creer_instance_random ~m:!m_ici ~n:!n_ici ~c:!c_ici ());
                plateau_ici := (Resolution.plateau_of_instance !instance_ici);
                afficher_instance !instance_ici;
                print_string "\nINIT OK !";
(*                read_line ();*)
                ) )
        ~f_end:(fun () -> (Graphics.clear_graph(); print_string "\nJ'espere que ca vous a fait rire :D !"Graphics.close_graph (); zenity_info "Merci d'avoir utilise ce programme ecrit par <b>Lilian BESSON</b>.\n\tProjet Puzzle - ENS de Cachan 2012 (c) et (r)."; exit(0);))
        ~f_key:(trait_char)
        ~f_mouse:(fun x y -> 
        (
                                  let piece_courante = (ask_piece ~c:!c_ici ()) in
                                  let (i0, j0) = ( ((x - 10) / (2*taille_piece+4)), ((y - 10) / (2*taille_piece+4))) in
                                  let p = (Resolution.matrix_of_list ~n:!n_ici ~m:!m_ici ~l:(!instance_ici.pi)) in
                                  p.(i0).(j0) <- piece_courante;
                                  instance_ici := { ci = !c_ici; ni = !n_ici; mi = !m_ici; pi = (Resolution.list_of_matrix p)};
                                  actualise_taille ~s:taille_piece ~n:!n_ici ~m:!m_ici ();
                                  afficher_instance !instance_ici;
                                  titre_ic ~c:!c_ici ~n:!n_ici ~m:!m_ici;
                                  )
                )
        ~f_except:(fun e -> ())) ;;

(*--------------------------------------------------------------------------------------------------------------------------------------------- *)
if (!(Sys.interactive)) then ex();;
(*!*)(* ESSAIS *)

(*Graphics.open_graph " 1000x700";;*)
(*Graphics.set_window_title "Projet Puzzle";;*)
(*try (aff_random ~m:6 ~n:6 ~c:6; ()) with Graphics.Graphic_failure "fatal I/O error" -> () ;;*)
(*if (!(Sys.interactive)) then let i = read_line () in ();;*)

(*interface_complete ();;*)
(*Graphics.close_graph ();;*)

(* Si on a pas lancé en mode toplevel, on quitte l'interpréteur dans le cas où il a été ouvert. (si on a lancé le programme par un 'monocaml -init interface.ml'). *)
(*if not(!(Sys.interactive)) then exit(0);;*)

(** Aide de l'interface graphique :

Ce programme est écrit en Ocaml, et utilise le module graphique de la distribution standard Graphics pour l'affichage, et l'outil Zenity, disponible gratuitement sous Linux (''$ sudo apt-get install zenity'' pour le récupérer).

Durant la boucle intéractive, les touches suivantes déclenchent les actions suivantes : h : affiche ce message d'aide, 'enter' : efface la fenêtre, 'e' fait la meme chose (le code de la touche 'enter' varie selon le mode d'exécution). u : change le nombre de couleurs utilisées, c : change l'échelle de couleur utilisée, m : change le nombre de colonnes des instances générées, n : change le nombre de lignes des instances générées, 'Escape': quitte proprement et enregistre l'instance / le plateau courant dans un fichier de sauvegarde caché, i : sauvegarde l'image de la fenêtre dans une image JPEG externe, en demandant un nom de fichier (demande confirmation avant écrasement). Utilise l'outil 'import', disponible gratuitement sous Linux (''$ sudo apt-get install imagemagick'' pour le récupérer), s : sauvegarde l'instance / le plateau courant affiché à l'ecran dans un fichier texte externe, en demandant un nom de fichier (demande confirmation avant écrasement). b : ouvre une instance déjà sauvegardée, en utilisant le navigateur de document de Zenity. 'space' : génére une nouvelle instance aléatoire avec les paramètres courant, et l'affiche, w : génére une nouvelle instance aléatoire, ayant une solution, avec les paramètres courant, et l'affiche, y : génére une solution aleatoire, avec les paramètres courant, et l'affiche, p : demande de choisir une pièce, puis une position pour cette pièce, et enfin confirmation pour la placer dans l'instance courante. Redessine celle ci ensuite. Un clic de la souris sur une position de pièce fait la même chose. v : pour vérifier la validité du plateau dessiné à l'écran, et afficher le résultat (valide, ou une preuve de non validité) dans une fenêtre informative. r : résoud l'instance courante affichée à l'écran,

Appuyez sur Echap pour fermer cette fenêtre d'aide et retourner au programme. Evitez de quitter brutalement, la gestion souple d'une 'fatal error' n'est pas encore au point (ceci est valable pour tout le programme). *)



if (!(Sys.interactive)) then ex();;