//Tom Monin et Clément Martins
//Class pour des fonctiononalité générale de jeu
//V4
import java.io.*;
import java.util.*;
import java.awt.event.*;
import java.io.ObjectOutputStream;
import java.awt.*;
import javax.swing.*;
import java.time.*;

public class plateau{
	//atribut représentant le haut a droite du menu
	private paintMenuJeu logo;

	//attribut de la fenetre
	private JFrame fenetre;

	//attribut de l'observateur du logo
	private observateurSAV observateur;

	//attribut représentant le nombre de ligne, collonne, bombe et minute
	private int ligne, collonne, bombe, minute;

	//attribut représentant les 3 case et haut a gauche du menu avec le score
	private paintMenuJeu[] tabScore= new paintMenuJeu[3];

	//notre tableau de Case correspondant a l'ensemble des Case du jeu
	private Case[][] tableau;

	//date a laquel cette partie a été lancer/relancer
	private LocalTime temps;

	//constructeur
	//@params (la fenetre)
	public plateau(JFrame fenetre0){
		this.fenetre=fenetre0; //on récupere la fenetre deja crée
		this.ligne=-1;	//initialement a -1 (valeur sentinelle)
		this.collonne=-1; 	//initialement a -1 (valeur sentinelle)
		this.bombe=-1;		//initialement a -1 (valeur sentinelle)
	}


	//-------------------> Debut Seter et Getteur <-------------------------
	public int getLigne(){
		return this.ligne;
	}
	public int getCollonne(){
		return this.collonne;
	}
	public int getBombe(){
		return this.bombe;
	}
	public JFrame getFenetre(){
		return this.fenetre;
	}
	public void setCollonne(int n){
		this.collonne=n;
	}
	public void setLigne(int n){
		this.ligne=n;
	}
	public void setBombe(int n){
		this.bombe=n;
	}
	public LocalTime getTemps(){
		return this.temps;
	}
	public int getMinute(){
		return this.minute;
	}
	//-------------------------> fin Setter et Getteur <-----------------------------

	//-------------------------Fonction plaçant les bombes aléatoirement------------------------

	private void setAllBombe(){
		Random rand = new Random();
		//on répète le nombre de fois le nombre de bombe a placer
		for(int i=0; i<this.bombe; i++){
			//on genere 2 chiffre aléatoire(ligne et collonne)
			int x=rand.nextInt(this.ligne);
			int y=rand.nextInt(this.collonne);
			//si il n'y a pas de bombe ici
			if(this.tableau[x][y].getBombe()==false){
				//on en place une
				this.tableau[x][y].setBombe();
			}else{
				//autrement nous recomencerons 1 fois de plus le tirage pour ne pas modifier le nombre de bombe
				i--;
			}
		}
	}
	//-------------------------Fonction mettant le nombre de bombe voisin a chaque Case------------------

	public void setAllVoisin(){
		//nous parcourons le tableau du jeu
		for(int i=0; i<this.tableau.length; i++){
			for(int t=0; t<this.tableau[i].length; t++){
				int voisin=0;
				//nous regardons dans les cases adjacentes(si elles existe) si elle sont des bombes
				if(i>0){
					if(this.tableau[i-1][t].getBombe()==true){
						//si elle le sont alors nous augmentons le nombre de voisin
						voisin++;
					}
					if(t>0){
						if(this.tableau[i-1][t-1].getBombe()==true){
							voisin++;
						}
					}
					if(t<this.tableau[i].length-1){
						if(this.tableau[i-1][t+1].getBombe()==true){
							voisin++;
						}
					}
				}
				if(i<this.tableau.length-1){
					if(this.tableau[i+1][t].getBombe()==true){
						voisin++;
					}
					if(t>0){
						if(this.tableau[i+1][t-1].getBombe()==true){
							voisin++;
						}
					}
					if(t<this.tableau[i].length-1){
						if(this.tableau[i+1][t+1].getBombe()==true){
							voisin++;
						}
					}
				}
				if(t>0){
					if(this.tableau[i][t-1].getBombe()==true){
						voisin++;
					}
				}
				if(t<this.tableau[i].length-1){
					if(this.tableau[i][t+1].getBombe()==true){
						voisin++;
					}
				}
				//finalement nous mettons pour cette case sont nombre de voisin
				this.tableau[i][t].setVoisin(voisin);
			}
		}
	}

	//-------------------------------Fonction qui verifie l'etat de jeu(perdu, gagner)------------------------

	public int etatDeVictoire(){
		//variable pour une condition
		int condition=0;
		//nous parcourons le tableau du jeu
		for(int i=0; i<this.tableau.length; i++){
			for(int t=0; t<this.tableau[i].length; t++){
				//si une case n'est pas visible mais que ce n'est pas une bombe
				if(this.tableau[i][t].getVisibiliter()==false && this.tableau[i][t].getBombe()==false){
					//nous augmentons la conditions pour montrer que la partie est toujour en cours
					condition++;
				}
				//si une bombe est visible
				if(this.tableau[i][t].getVisibiliter()==true && this.tableau[i][t].getBombe()==true){
					// le joueur a perdu
					this.removeListener();
					this.perdu();
					//on retourne une valeur représentant la défaite: -1
					return -1;
				}
			}
		}
		//si la partie n'est pas fini
		if(condition>0){
			//on retourne 0 ici comme une valleur null
			return 0;
		}
		// le joueur a gagner
		this.removeListener();
		this.gagner();
		//sinon le joueur a donc gagner on renvoie 1
		return 1;
	}
	//-----------------------------------Fonction après victoire/defaite pour enlever les observateur a chaque Case--------------------------------

	private void removeListener(){
		//on parcour le tableau du jeu
		for(int i=0; i<this.tableau.length; i++){
			for(int t=0; t<this.tableau[i].length; t++){
				//on récupere le tableau d'observateur
				MouseListener[] tabListener;
				tabListener=this.tableau[i][t].getMouseListeners();
				//que nous parcourons
				for(int longeur=0; longeur<=tabListener.length-1; longeur++){
					//on enleve a chaque case les observateur 1 par 1
					this.tableau[i][t].removeMouseListener(tabListener[longeur]);
				}	
			}
		}
	}
	//-------------------------------------Fonction des modification a faire si le joeur a perdu-------------------------------------
	private void perdu(){
		//on modifie l'affichage du menu du haut par le choix 5(tete rouge)
		for(int i=0; i<tabScore.length; i++){
			this.tabScore[i].setChoix(5);
		}
		//puis nous affichons toutes les case pour que le joueur puisse voir la grille
		for(int i=0;i<this.tableau.length;i++){
			for(int t=0;t<this.tableau[i].length;t++){
				this.tableau[i][t].setVisibiliter(true);
				this.tableau[i][t].repaint();
			}
		}
		//enfin nous affichons le logo rejouer
		this.paintLogo();
	}
	//-------------------------------------Fonction des modification a faire si le joeur a gagner-------------------------------------
	private void gagner(){
		//on modifie l'affichage du menu du haut par le choix 6(tete vert)
		for(int i=0; i<tabScore.length; i++){
			this.tabScore[i].setChoix(6);
		}
		//nous affichons le logo rejouer
		this.paintLogo();

	}
	//-------------------------------------Fonction pour modifier le Score des bombes restantes--------------------------------------
	public void setScore(int n){
		//on parcoure le menu du haut avec le score et nous lui ajoutons l'entier entrée dans l'apel
		for(int i=0; i<tabScore.length; i++){
			tabScore[i].setScore(tabScore[i].getScore()+n);
		}
	}
	//------------------------------------> Fonction pour afficher le logo rejouer <----------------------------
	private void paintLogo(){
		//on modifie l'aspect de l'ancien logo
		this.logo.setChoix(7);
		//et la fonction de son observateur
		this.observateur.setFonction(true);
	}

	//------------------------------------> Fonction pour lancer une nouvelle partie <-----------------------

	public void newGame(){
		//initialisation du tableau de Case
		this.tableau=new Case[ligne][collonne];
		for(int i=0; i<ligne; i++){
			for(int t=0; t<collonne; t++){
				this.tableau[i][t]= new Case();
				this.tableau[i][t].setPreferredSize(new Dimension(50,50));
				//nous ajoutons aussi a chaque case son observateur de case
			}
		}
		//nous disposons les bombe dans le jeu
		this.setAllBombe();
		//maitenant que les bombe sont mise nous pouvons modifier le nombre de voisin des cases
		this.setAllVoisin();
		//les minutes sont donc a 0
		this.minute=0;
		//on lance la partie avec le tableau de Case initialiser et le nombre de bombe en score(aucune Case n'est encore suspecter)
		this.launchGame(this.bombe);
	}

	//------------------------------------> Fonction pour sauvegarder la partie <-------------------------

/*

Mettre les bloc try catch au bon endroit 
*/



	public void save(){
		//si la fonction est appeler et donc
		int resultat=JOptionPane.showConfirmDialog(this.fenetre, "Voulez vous sauvegarder?");
		if(resultat!=JOptionPane.YES_OPTION){
			if(resultat==JOptionPane.NO_OPTION){
				this.fenetre.dispose();
			}
		}else{
			//les fonctions vont lever des Exeption nous devons donc mettre un try Catch
			try{
				//nous initialison le outputStream en créant le fichier "sauvegarde.data"
				FileOutputStream sav = new FileOutputStream(new File("sauvegarde.data"));
      			try{
      				//nous allons sérialiser le tableau de Case
    	  			ObjectOutputStream oos = new ObjectOutputStream(sav);
	      			//nous écrivons donc tout d'abord la taille de celui-ci
					oos.writeInt(this.ligne);
					oos.writeInt(this.collonne);
					//puis nous parcourons le tableau en sérialisant toute les Case
					for(int i=0; i<this.ligne; i++){
						for(int t=0; t<this.collonne; t++){
							oos.writeObject(this.tableau[i][t]);
						}
					}
					//nous écrivons ensuite le score Maximum(nombre de bombe) pour éviter de regarder toutes les Case plus tard
					oos.writeInt(this.tabScore[0].getScoreMax());
					//puis le score a l'instant T
					oos.writeInt(this.tabScore[0].getScore());
					//ainsi que le nombre de minute que le joueur a deja passer a résoudre le démineur
					//si le joeur a pris plus de 1 heure
					if(LocalTime.now(ZoneId.systemDefault()).getHour()-this.temps.getHour()>0){
						//on multiplie le nombre d'heure par 60 et ajoutons son nombre de minutes dans celle-ci
						oos.writeInt(LocalTime.now(ZoneId.systemDefault()).getHour()-this.temps.getHour()*60+this.temps.getMinute());
					}else{
						oos.writeInt(this.temps.getMinute());
					}
					
					//nous fermons la sauvegardes
					try{
						oos.close();
					//si erreur dans la fermuture donc lors de l'ecriture final
					}catch(IOException e){
						//nous affichons un pop-up pour pevenir l'utilisateur
						JOptionPane.showMessageDialog(this.fenetre, "erreur lors de l'ecriture final de la sauvegarde");
						this.fenetre.dispose();
					}
				//si erreur lors de l'ecriture
				}catch(IOException e){
					//nous affichons un pop-up pour pevenir l'utilisateur
					JOptionPane.showMessageDialog(this.fenetre, "erreur lors de l'ecriture de la sauvegarde");
					this.fenetre.dispose();
				}
			//si fichier introuvable et donc (dossier du même nom, problème de droit d'accès...)
			}catch(FileNotFoundException e1){
				//nous affichons un pop-up pour pevenir l'utilisateur
				JOptionPane.showMessageDialog(this.fenetre, "erreur impossible d'accèder au fichier de sauvegarde");
				this.fenetre.dispose();
			}
			//autrement, nous fermons la fenetre et donc arrêtons l'applications et indiquons au joueur que tout c'est bien dérouler
			JOptionPane.showMessageDialog(this.fenetre, "Partie sauvegarder");
			this.fenetre.dispose();
		}

		
		
	}
	public void reprendrePartie(FileInputStream file){
		//initialisation de la variable score
		int score=0;
		//les fonctions vont lever des Exeption nous devons donc mettre un try Catch
		try{
      		//initialisation des collonnes avec le fichier
      		try{
      			//création de l'objet pour lire le fichier donner
      			ObjectInputStream ois = new ObjectInputStream(file);
				this.ligne=ois.readInt();
				this.collonne=ois.readInt();
				//on crée le tableau de Case avec les bonnes dimention
				this.tableau=new Case[this.ligne][this.collonne];
				//on parcoure celui-ci et remettons les Case avec la sauvegarde
				for(int i=0; i<ligne; i++){
					for(int t=0; t<collonne; t++){
						this.tableau[i][t]=(Case) ois.readObject();
					}
				}
				//nous initialison maintenant le nombre de bombe
				this.bombe=ois.readInt();
				//puis le score
				score=ois.readInt();
				//nous reprenons le nombre de minute que le joueur a deja jouer
				this.minute=ois.readInt();
				//nous ne positionons pas de try-catch sur le close car en lecture il ne représenterais pas un flush
				//et donc pouvons nous en passer
				ois.close();
				//nous pouvons maintenant lancer la partie sauvegarder
				this.launchGame(score);
				//si erreur dans la lecture du fichier
			}catch(IOException e2){
				//nous affichons un pop-up pour pevenir l'utilisateur que le fichier ne correspond sûrement plus a nos attente
				JOptionPane.showMessageDialog(this.fenetre, "fichier de sauvegarde corompu");
				//nous repositionons le joueur au choix du premier menu
				this.menuChoixTypePartie();
			}
		// si il ne trouve pas les classe voulu
		}catch(ClassNotFoundException e3){
			//nous affichons un pop-up pour pevenir l'utilisateur que le fichier ne correspond sûrement plus a nos attente
			JOptionPane.showMessageDialog(this.fenetre, "fichier de sauvegarde corompu");
			//nous repositionons le joueur au choix du premier menu
			this.menuChoixTypePartie();
		//par précaution nous capturons le reste des exeception
		}catch(Exception e){
			JOptionPane.showMessageDialog(this.fenetre, "fichier de sauvegarde imcomplet");
			this.menuChoixTypePartie();
		}
	}
	//----------------------------------------> Fonction pour Lancer une partie <--------------------------------------

	private void launchGame(int score){
		//nous sauvegardons la date du lancement avec l'attribut temps
		this.temps=LocalTime.now(ZoneId.systemDefault());
		//on ferme l'ancienne fenetre
		this.fenetre.dispose();
		//on en crée une nouvelles
		this.fenetre=new JFrame("démineur");
		// l'application se fermera que si on ferme la fenetre
		this.fenetre.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		//on l'affiche
		this.fenetre.setVisible(true);
		//on ajoute son observateur (pour savegarder la partie si on ferme la fenetre)
		this.fenetre.addWindowListener(new observateurFenetre(this));
		this.fenetre.setBackground(new Color(0,0,0));
		//nous découpons la fentre en 2 partie grâce au JPanel
		//le paneau du jeu
		JPanel panJeu = new JPanel();
		//le paneau du menu
		JPanel panMenu = new JPanel();
		panJeu.setBackground(new Color(0,0,0));
		panMenu.setBackground(new Color(0,0,0));
		//on met un gestionaires de mise en page grille avec le nombre de ligne+1 (pour le menu en haut) et le nombre de collonne
		panJeu.setLayout(new GridLayout(this.ligne,this.collonne));
		//pour le menu la mise en page ne dépend pas de la partie
		panMenu.setLayout(new GridLayout(1, 15));
		//on met en place le menu du haut et donc toute la première ligne
		for(int i=0; i<15; i++){
			paintMenuJeu menu= new paintMenuJeu(0, score, bombe);
			//nous mettons des dimensions convenable pour des Case
			menu.setPreferredSize(new Dimension(50,50));
			//les 3 premières case serons consacrés au score
			if(i==0){
				this.tabScore[0]=menu;
				menu.setChoix(1);
			}
			if(i==1){
				this.tabScore[1]=menu;
				menu.setChoix(2);
			}
			if(i==2){
				this.tabScore[2]=menu;
				menu.setChoix(3);
			}
			//et la derniere au logo
			if(i==14){
				menu.setChoix(4);
				this.logo=menu;
				observateurSAV observateur=new observateurSAV(menu, this);
				menu.addMouseListener(observateur);
				this.observateur=observateur;
			}
			//sinon le composant sera juste sobre
			panMenu.add(menu);
		}
		//on ajoute ensuite toutes les Case a la fenetre
		for(int i=0; i<ligne; i++){
			for(int t=0;t<collonne; t++){
				panJeu.add(this.tableau[i][t]);
				this.tableau[i][t].addMouseListener(new observateurCase(i, t, this.tableau, this));
			}
		}
		//on ajoute le menu en haut (NORTH)
		this.fenetre.add(panMenu, BorderLayout.NORTH);
		//le jeu au center (CENTER)
		this.fenetre.add(panJeu, BorderLayout.CENTER);
		//on ajuste la taille de la fenetre a son contenu
		this.fenetre.pack();
	}
	//---------------------> Fonction d'affichage du menu pour le Choix des Ligne, Collonne et Bombe <-----------------------------
	public void menuChoixLCB(){
		//nous fermons l'ancienne fenetre
		this.fenetre.dispose();
		//créons une nouvelle
		this.fenetre= new JFrame("démineur");
		this.fenetre.setLocation(0,0);
		this.fenetre.setSize(1600, 1000);
		this.fenetre.setVisible(true);
		this.fenetre.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		//nous utiliserons un gestionnaires avec 27 ligne(de 4 a 30 ligne/collonne) et 3 collonne
		fenetre.setLayout(new GridLayout(27,3));
		//initialisation des objet utiliser par des observateur plus tard
		paintChoix retenue= new paintChoix(-1, -1);
		paintChoix premier= new paintChoix(-1, -1);
		paintChoix retenue2= new paintChoix(-1, -1);
		paintChoix premier2= new paintChoix(-1, -1);
		JLabel texte1 = new JLabel("<=== ligne: ???");
		JLabel texte2 = new JLabel("collonne: ??? ===>");
		//aligment Central plus estetique
		texte1.setHorizontalAlignment(JLabel.CENTER);
		texte2.setHorizontalAlignment(JLabel.CENTER);
		JTextField nbombe = new JTextField("<entrer le nombre de bombe>");
		//nous ajoutons les Case
		for(int i=0; i<27; i++){
			//creons les composant
			paintChoix pinceau = new paintChoix(i, 1);
			paintChoix pinceau2 = new paintChoix(i, 2);
			if(i==0){
				//si c'est la première Case alors ça sera la première pour sa ligne
				premier=pinceau;
				premier2=pinceau2;
			}
			//nous leurs ajoutons un observateur
			pinceau.addMouseListener(new observateurChoix(pinceau, premier, texte1, this));
			pinceau2.addMouseListener(new observateurChoix(pinceau2, premier2, texte2, this));
			//positionons la retenue sur le nouveau pinceau
			retenue.setPaintChoix(pinceau);
			retenue=pinceau;
			retenue2.setPaintChoix(pinceau2);
			retenue2=pinceau2;
			//nous ajoutons la première case sur la collonne 1
			this.fenetre.add(pinceau);
			//la dexième collonne dependra de sa hauteur dans la ligne
			if(i==10){
				//texte sur les lignes
				this.fenetre.add(texte1);
			}
			if(i==12){
				//texte sur les collonnes
				this.fenetre.add(texte2);
			}if(i==26){
				//si c'est la dernière ligne, boutons valider
				JButton valider = new JButton("valider");
				valider.addActionListener(new observateurButtonEtText(this, 2, nbombe));
				this.fenetre.add(valider);
			}if(i==17){
				//boutons plus
				JButton plus = new JButton("+");
				plus.addActionListener(new observateurButtonEtText(this, 3, nbombe));
				this.fenetre.add(plus);
			}if(i==18){
				//texteArea qui représente le nombre de bombe
				nbombe.setHorizontalAlignment(JLabel.CENTER);
				nbombe.addActionListener(new observateurButtonEtText(this, 1, nbombe));
				this.fenetre.add(nbombe);
			}if(i==19){
				//boutons moins
				JButton moins = new JButton("-");
				moins.addActionListener(new observateurButtonEtText(this, 4, nbombe));
				this.fenetre.add(moins);
			}if(i==16){
				//juste le texte
				JLabel texte3 = new JLabel("Nombre de Bombe");
				texte3.setHorizontalAlignment(JLabel.CENTER);
				this.fenetre.add(texte3);
			}if(i!=10 && i!=12 && i!=26 && i!=17 && i!=18 && i!=19 && i!=16){
				//autrement nous ajoutons un composant inutile
				this.fenetre.add(new JLabel());
			}
			//enfin nous ajoutons sur la 3ième collonne le 2ième paintChoix
			this.fenetre.add(pinceau2);
		}
	}
	//-----------------------------------------> Fonction pour l'affichage du premier menu (nouvelle partie/reprendrePartie/quitter) <---------------------
	public void menuChoixTypePartie(){
		//nous créons une nouvelles fenetre
		this.fenetre.dispose();
		this.fenetre= new JFrame("démineur");
		this.fenetre.setLocation(0,0);
		this.fenetre.setSize(1600, 1000);
		this.fenetre.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		//nous séparons la fenetre en 3 collonne
		this.fenetre.setLayout(new GridLayout(1,3));
		//pour chaque colonne nous lui ajouterons un paint choix
		paintChoix men1=new paintChoix(0, 8); 	//pour quitter la partie
		paintChoix men2=new paintChoix(0, 9);	//pour faire une nouvelle partie
		paintChoix men3=new paintChoix(0, 10);	//pour reprendre la partie

		//nous ajoutons ces composant
		this.fenetre.add(men3);
		this.fenetre.add(men2);
		this.fenetre.add(men1);

		//nous ajoutons des observateurs a nos composant
		men1.addMouseListener(new observateurChoix(men1, null, null, this));
		men3.addMouseListener(new observateurChoix(men3, null, null, this));
		men2.addMouseListener(new observateurChoix(men2, null, null, this));
		this.fenetre.setVisible(true);
	}
} 