package network;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Vector;
import other.MsgBox;
import other.PathHelp;

/**
 * ObjManager Klasse 
 * Verwaltet die in den Speicher geladenen Transportobjekte u. 
 * beinhaltet die nötigen Funktionen zu deren Verwaltung
 */
public class ObjManager{
	
	// Attribute
	private Vector vTransObj = new Vector();
	private ResultSet rs;
	
	private FileReader fReader;
	private FileWriter fWriter;
	private BufferedReader bReader;
	private BufferedWriter bWriter;
	
	private DBModule dbModule;
	private Queue comQueue;

	/**
	 * Konstruktor
	 * @param cQueue Queue für Commands
	 * @param dbMod DataAccessObject für MySQL Datenbank
	 */
	public ObjManager(Queue cQueue, DBModule dbMod){
		comQueue = cQueue;	
		dbModule = dbMod;
	}
	
	/* ##########################################################################
	 * 						ÖFFENTLICHE METHODEN
	 * ##########################################################################*/	
	
	/**
	 * Lädt alle Files aus dem Directory in den TransObj Vector
	 * @param pPath Pfad aus dem geladen werden soll
	 */
	public void loadFiles(String pPath){
		String sName="",sLine="";
		char[] cLine;
		float fRad=0,fHeight=0;
		int iLine,iNr;
		boolean bAsked=false,bOverwrite=false;
	 
		File dir = new File(pPath);
		String[] sFiles = dir.list();
	 
		//Schleife die alle Files durchläuft
		for(int i=0; sFiles!=null && i<sFiles.length;i++){	
			iLine = 0;
    
			//versteckte Dateien werden ausgelassen (1. Zeichen = '.')
			if(sFiles[i].substring(0,1).equals(".")) continue;
	  
			//Nummer aus Filenamen ermitteln
			try{
				iNr = Integer.parseInt(sFiles[i].substring(0,sFiles[i].indexOf(".")));
			}catch(NumberFormatException e){
				MsgBox.showError("Der Dateiname <"+dir+PathHelp.pathSep()+sFiles[i]+"> ist ungültig!");
				continue;
			}
	 
			//Datei Lesen
			try{ 
				fReader = new FileReader(dir+PathHelp.pathSep()+sFiles[i]);
				bReader = new BufferedReader(fReader);
			}catch(FileNotFoundException e){
				MsgBox.showError("Die Datei <"+dir+PathHelp.pathSep()+sFiles[i]+"> konnte nicht gefunden werden!");
				continue;
			}
	 
			// Variable, damit man innerhalb der nächsten Schleife
			// die äußere Schleife fortsetzen kann
			boolean cont_fur=false;
			
			//Schleife, die alle Zeilen durchläuft
			for(int x=0;x<18;x++)
			{

				try{ //eine Zeile einlesen
					sLine = bReader.readLine();
				}catch(IOException e){
					MsgBox.showError("Datei <"+dir+PathHelp.pathSep()+sFiles[i]+"> konnte nicht gelesen werden!");
					cont_fur=true;
					break;
				}
	  	 
				//bei leeren Zeilen zur nächsten springen
				if(sLine.length()==0) continue;
				cLine = sLine.toCharArray();

				try{
					//Kontrolle ob Zeile mit Parameter gefunden wurde
					if(cLine[0]!='#'){ //Zeile mit Param suchen
						switch(iLine){
						case 0: //Name
							sName=sLine.substring(5,sLine.length());
							break;
						case 1: //Radius
							fRad=Float.parseFloat(sLine.substring(7,sLine.length()));
							break;
						case 2: //Höhe
							fHeight=Float.parseFloat(sLine.substring(7,sLine.length()));
							break;
						}
						iLine++;
					}
				}catch(NumberFormatException e){
					MsgBox.showError("Ungültige Werte in der Datei <"+dir+PathHelp.pathSep()+sFiles[i]+">");
					cont_fur=true;
					break;
				}
			}
			
			// Wenn innerhalb der Schleife wegen einem Fehler abgebrochen wurde,
			// diese Datei nicht mehr bis zum Ende auslesen
			if(cont_fur) continue;
			
			//bei gleichen TransObjs fragen ob überschreiben
			if(this.getIndex(iNr)>=0 && !bAsked){
				bOverwrite=this.askOverwrite('l');	
				bAsked=true; //nur 1x fragen
			}

			//Kontrolle, ob Datei richtig geladen wurde
			if(iLine==3){
				//vorhandenes ersetzen, wenn bOverwrite==1 
				if(this.getIndex(iNr)>=0 && bOverwrite){
					this.addTransObjAt(this.getIndex(iNr),new TransObj(iNr,sName,fRad,fHeight));
				}
				//einfach hinzufügen, da noch nicht vorhanden
				else if(this.getIndex(iNr)<0){ 
					this.addTransObj(new TransObj(iNr,sName,fRad,fHeight));
				}
			}else{
				MsgBox.showError("Die Datei <"+dir+PathHelp.pathSep()+sFiles[i]+"> konnte nicht richtig geladen werden!");
			}
			
			//Files schließen
			try{
				bReader.close();
				fReader.close();
			}catch(IOException e){
				System.out.println("Die Datei <"+dir+PathHelp.pathSep()+sFiles[i]+"> konnte nicht geschlossen werden!");	
			}
		}
	}
	
	/**
	 * speichert die TransObjs aus dem Speicher in ein 
	 * Verzeichnis nach Wahl als ".obj-Files"
	 * @param pPath Pfad in dem gespeichert werden soll
	 */
	public void saveFiles(String pPath){

		String sLine,sPath;
		char cLine[];
		int iLine=0;
		boolean bAsked=false,bOverwrite=false;
		TransObj t;

		//Kontrolle ob blank-File vorhanden ist
		File cFile = new File(PathHelp.specFileBlankObj());
		if(!cFile.exists()){
			MsgBox.showError("Die Datei <blank.obj> konnte nicht gefunden werden!");
			return;
		}

		// Pfad aktualisieren
		if(!pPath.endsWith(PathHelp.pathSep())) pPath+=PathHelp.pathSep();
		
		//durchläuft alle Files
		for(int i=0;i<this.count();i++){
	 
			iLine=0;
			t=this.getTransObjAt(i);
	 
			//Pfad zusammenstellen u. File kopieren
			sPath=pPath+t.getNr()+".obj";	

			//Kontrolle ob File bereits existiert u. gegebenenfalls
			//OptionPane anzeigen
			File fFile = new File(sPath);
			if(fFile.exists() && !bAsked){
				bOverwrite=this.askOverwrite('s');	
				bAsked=true; //nur 1x fragen
			}
	 
			//wenn File existiert u. nicht überschrieben wird, mit
			//nächstem File weiter machen
			if(fFile.exists() && !bOverwrite) continue;
	 
			//Dateien öffnen
			try{ 
				fReader = new FileReader(PathHelp.specFileBlankObj());
				bReader = new BufferedReader(fReader);
			}catch(IOException e){
				MsgBox.showError("Die Datei <blank.obj> konnte nicht geöffnet werden!");
				return; //abbrechen
			}
			try{ 
				fWriter = new FileWriter(sPath);
				bWriter = new BufferedWriter(fWriter);
			}catch(IOException e){
				MsgBox.showError("Die Datei <"+sPath+"> kann nicht erzeugt werden!");
				return; //abbrechen
			}

			//Schleife, die alle Zeilen durchläuft
			for(int x=0;x<18;x++){
				try{
					sLine=bReader.readLine();
					if(x==1) sLine=sLine+" "+t.getNr(); //Nummer einfügen
	  
					//Kontrolle ob leere Zeile
					if(sLine.length()==0){
						bWriter.write("\n");
						continue;
					}
					cLine=sLine.toCharArray();
					//Kontrolle ob Zeile mit Parameter gefunden wurde
					if(cLine[0]!='#'){ //Zeile mit Param suchen
						switch(iLine){
						case 0: //Name
							bWriter.write(sLine+t.getName()+"\n");
							break;
						case 1: //Radius
							bWriter.write(sLine+t.getRad()+"\n");
							break;
						case 2: //Höhe
							bWriter.write(sLine+t.getHeight()+"\n");
							break;
						}
						iLine++;
					}
					else{
						bWriter.write(sLine+"\n");
					}
				}catch(IOException e){
					MsgBox.showError("Datei <"+sPath+"> konnte nicht geschrieben werden!");
					return;
				}
			}
			//Files schließen
			try{
				bWriter.close();
				bReader.close();
				fWriter.close();
				fReader.close();
			}catch(IOException e){
				System.out.println("Die Dateien konnten nicht geschlossen werden");	
			}
		}
		MsgBox.showInfo("Die Dateien wurden gespeichert.");
	}
    
	/**
	 * Synchronisiert die Daten nach Programmstart u. bringt
	 * die TransObjs aus DB u. Vector auf den geleichen Stand
	 *
	 */
	public void syncPrgStart(){
		// Sicherheitsabfrage
		if(!dbModule.isConnected()){
			dbModule.raiseNotification();	
			return;
		}
		
		boolean bOwDB = false; //true => DB TransObj werden überschrieben
		boolean bOWDB_asked = false;
		
		int iNr = 0, iPos = -1;
		
		// ResultSet holen
		rs = dbModule.loadData(); 
	 
		try{
			// Daten auslesen und speichern
			while(rs.next()){	 
				iNr = Integer.parseInt(rs.getString("Nr").trim());
				iPos = this.getIndex(iNr);

				// im Vector bereits vorhanden
				if(iPos>=0){
					// Abfragen, was getan werden soll
					if(!bOWDB_asked){
						bOwDB = askSync();
						bOWDB_asked = true;
					}
					// nur übernehmen, wenn die DB nicht überschrieben werden soll
					if(!bOwDB){
						TransObj t = (TransObj)vTransObj.elementAt(iPos);
						t.setName(rs.getString("Name").trim());
						t.setRad(Float.parseFloat(rs.getString("Radius").trim()));	
						t.setHeight(Float.parseFloat(rs.getString("Height").trim()));
						t.setSynchronized(true);
					}
				}
				// im Vector noch nicht vorhanden
				else if(iPos<0){
					TransObj t = new TransObj(iNr,(rs.getString("Name").trim()),
							(Float.parseFloat(rs.getString("Radius").trim())),
					  		(Float.parseFloat(rs.getString("Height"))));
					t.setSynchronized(true);
					this.addTransObj(t);
				}
			}
			
			// Alle, die nur im Vector sind, durchlaufen und gegebenenfalls
			// in Datenbank einfügen
			for(int i=0;i<this.count();i++){
				if(!((TransObj)vTransObj.elementAt(i)).isSynchronized()){
					// In DB bereits vorhanden => nur ändern
					if(this.isInDB(((TransObj)vTransObj.elementAt(i)).getNr()))	
						dbModule.updateObjInDB((TransObj)vTransObj.elementAt(i));		   	  
					// In DB noch nicht vorhanden => einfügen
					else
						dbModule.defineObjInDB((TransObj)vTransObj.elementAt(i));
					// Sync auf true setzen 
					((TransObj)vTransObj.elementAt(i)).setSynchronized(true); 
				}
			}
		}catch(SQLException e){
			e.printStackTrace(); // Debug
			MsgBox.showError("Fehler beim Synchronisieren!"); 	 	
		}
		//Command REFRESH_DATABASE in Queue hinzufügen
		comQueue.enqueue(new Command(NetPack.REFRESH_DATABASE));
	}
	
	/**
	 * Synchronisiert die Daten aus der Tabelle mit den Daten
	 * aus der Datenbank
	 */
	public void syncTransObj(){
		// Sicherheitsabfrage
		if(!dbModule.isConnected()){
			dbModule.raiseNotification();
			MsgBox.showError("Fehler: Datenbank nicht verbunden.");
			return;
		}
		
		int iNr=0;
		
		// Alle TransObj durchlaufen	
		for(int i=0;i<this.count();i++){
			iNr=((TransObj)vTransObj.elementAt(i)).getNr();
	   
			// TransObj nicht in DB vorhanden => erzeugen
			if(!isInDB(iNr)){
				dbModule.defineObjInDB((TransObj)vTransObj.elementAt(i));
				((TransObj)vTransObj.elementAt(i)).setSynchronized(true);
			}
			// TransObj wurde geändert
			else if(!((TransObj)vTransObj.elementAt(i)).isSynchronized()){
				dbModule.updateObjInDB(((TransObj)vTransObj.elementAt(i)));
				((TransObj)vTransObj.elementAt(i)).setSynchronized(true);
			}
			// TransObj wird gelöscht
			else if(!((TransObj)vTransObj.elementAt(i)).isDeleted()){
				dbModule.deleteObjInDB(iNr);
				this.removeTransObj(iNr); //TransObj aus Vector löschen
			}
		}
		//Command REFRESH_DATABASE in Queue hinzufügen
		comQueue.enqueue(new Command(NetPack.REFRESH_DATABASE));
	}
	
	/**
	 * Fügt ein Transportobjekt in den Vector hinzu
	 * @param t TransObj das hinzugefügt werden soll
	 */
	public void addTransObj(TransObj t){
		vTransObj.add(t);	
	}
	
	/**
	 * Fügt ein Transportobjekt in den Vector hinzu
	 * @param iPos Index an dem das Objekt eingefügt werden soll
	 * @param t TransObj das hinzugefügt werden soll
	 */
	public void addTransObjAt(int iPos,TransObj t){
		vTransObj.remove(iPos);	
		vTransObj.add(iPos,t);	
	}
	
	/**
	 * löscht ein TransObj aus dem Vector
	 * @param iNr Nummer des TransObj
	 */
	public void removeTransObj(int iNr){
		int iPos=this.getIndex(iNr);
		if(iPos>=0)
			vTransObj.remove(iPos);	
	}
	
	/**
	 * liefert den Vectorindex eines TransObj anhand der Nummer
	 * @param iNr Nummer des TransObj
	 * @return die Nummmer wenn gefunden, sonst 0
	 */
	public int getIndex(int iNr){
		 for(int i=0;i<this.count();i++)
			  if((((TransObj)vTransObj.elementAt(i)).getNr())==iNr) return i;
		return -1;	
    }
	
	/**
	 * Liefert ein gewünschtes Transport Objekt zurück
	 * @param iNr Nr des TransObj
	 * @return das gewünschte Transobj
	 */
	public TransObj getTransObj(int iNr){
		for(int i=0;i<this.count();i++){
			if(((TransObj)vTransObj.elementAt(i)).getNr()==iNr)
				return (TransObj)vTransObj.elementAt(i);
		}
		return null;
	}

	/**
	 * Liefert ein gewünschtes Transport Objekt zurück
	 * @param iPos Index des TransObj
	 * @return das gewünschte Transobj
	 */
	public TransObj getTransObjAt(int iPos){
		return (TransObj)vTransObj.elementAt(iPos);
	}	
	
	/**
	 * Liefert die Anzahl der gespeicherten Transportobjekte
	 * @return Anzahl der TransObjs
	 */
	public int count(){
		return vTransObj.size();	
	}
	
	 /**
	  * schaut, ob ein TransObj in der Datenbank vorhanden ist
	  * od. nicht
	  * @param iNr Nummer des TransObj
	  * @return true wenn vorhanden, sonst false
	  */
	public boolean isInDB(int iNr){
		return dbModule.isInDB(iNr);	
	}
	
	/**
	 * Liefert die Nr des nächstmöglichen freien TransObj
	 * = die nächst Nummer, die ein neues Transport Objekt haben kann 
	 * @return die freie Nummer
	 */
	public int getFreeNr(){
		int i=1;
		while(this.getTransObj(i)!=null)
			i++;
		return i;
	}

	/* ##########################################################################
	 * 						HILFSMETHODEN
	 * ##########################################################################*/	
	
	/**
	 * zeigt ein OptionPane mit der Frage, ob überschrieben
	 * werden soll od. nicht an
	 * @param cType "l" beim Laden und "s" beim Schreiben
	 * @return 1 wenn überschreiben, sonst 0
	 */
	private boolean askOverwrite(char cType){
		if(cType=='l') //laden
			return MsgBox.showYesNoQuestion("Wollen Sie bereits existierende Transportobjekte überladen?");
		else //schreiben
			return MsgBox.showYesNoQuestion("Wollen Sie bereits existierende Dateien überschreiben?");
	}
	
	/**
	 * zeigt ein OptionPane mit der Frage, ob Dateien aus Tabelle
	 * od. Datenbank priorität haben
	 * @return 1 wenn überschreiben, sonst 0
	 */
	private boolean askSync(){
		return MsgBox.showYesNoQuestion("Synchronisation der Transport-Objekte: " +
				"Wollen Sie die Daten in der Datenbank überladen?");
	}
}
