package backend;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileWriter;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;

import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;

public class LibSync {

	/*
	 * TO-DO list
	 *  
	 * 
	 */
	
	public static final int MOBILE_LIB = 2;
	public static final int ITUNES_LIB = 3;
	public static final int LIBSTATE_BOTH_LIBS_OK = 1;
	public static final int LIBSTATE_ITUNES_LIB_OK_NO_MOBILE_LIB = 2;
	public static final int LIBSTATE_NO_ITUNES_LIB_NO_MOBILE_LIB = -1;
	public static final int LIBSTATE_NO_ITUNES_LIB_MOBILE_LIB_OK = -2;
	public static final int LIBSTATE_ANALYZING = 0;
	public static final int LIBSTATE_SYNCING = 3;
	public static final int LIBSTATE_SYNC_OK = 4;
	
	private ItunesLibrary library;
	private ItunesLibrary mobileLibrary;
	private Settings settings;
	private int totalToSync;
	private int alreadySynced;
	private int totalToDelete;
	private int alreadyDeleted;
	private ActionListener analysisCompleteListener;
	private boolean syncInProgress;
	private boolean syncCompleted;

	public LibSync() throws FileNotFoundException {
		this.settings = new Settings(new File(Settings.CONFIGFILE_NAME));
		this.loadLibs();
		this.resetProgressStats();
	}
	
	public LibSync(String lib, String mobilib) throws FileNotFoundException {
		
		if (lib != null)
			this.setSetting(Settings.ITUNES_LIBRARY, lib);
		if (mobilib != null)
			this.setSetting(Settings.MOBILE_LIBRARY_DIR, mobilib);

		this.loadLibs();
		this.resetProgressStats();

	}
	
	private void resetProgressStats() {
		this.alreadySynced = 0;
		this.totalToSync = 0;
		this.alreadyDeleted = 0;
		this.totalToDelete = 0;
	}
	
	public void reanalyze() throws FileNotFoundException {
		this.loadLibs();
		Debug.println(this.analysisCompleteListener+"");
	}
	
	public boolean analysisReady() {
		return this.library.fileAnalyzed() && this.mobileLibrary.fileAnalyzed();
	}
	
	public boolean analysisNotSuccessful() {
		return this.library.fileAnalyzed() && this.library.newLibFileToBeCreated();
	}

	public boolean syncCompleted() {
		return (this.alreadySynced == this.totalToSync);
	}

	public void setSetting(String key, String value) {
		if (this.settings == null) {
			this.settings = new Settings();
		}
		this.settings.setValue(key, value);

	}

	public void setWholeLibSync(boolean flag) {
		//System.out.println("Whole lib sync: " + flag);
		this.library.setWholeLibrarySync(flag);
		this.setSetting(Settings.SYNC_ENTIRE_LIBRARY, flag ? "yes" : "no");
	}

	public String getSetting(String key) {
		return this.settings.getValue(key);
	}
	
	public long getFreeSpaceOnDevice() {
		File f = new File(this.getSetting(Settings.MOBILE_LIBRARY_DIR));
		// if f does not exists, getFreeSpace returns 0
		while (!f.exists() && f.getParentFile() != null) {
			f = f.getParentFile();
		}
		long size = f.getFreeSpace();
		return size;
	}
	
	public Set<String> getPlaylists() {
		return this.library.getPlaylists();
	}
	
	public void libNotFound(ItunesLibrary lib) {
		if (lib == this.mobileLibrary) { // mobile lib ok to be missing
			this.mobileLibrary.forceFileAnalyzed(true);
		} else {
			//System.out.println("Forcing library file analyzed");
			//System.out.println(Thread.currentThread());
			this.library.forceFileAnalyzed(true);
		}
	}
	
	public boolean mobileLibFileToBeCreated() {
		return this.mobileLibrary.newLibFileToBeCreated();
	}
	
	public boolean itunesLibNotFound() {
		//System.out.println("hii: "+this.library.newLibFileToBeCreated());
		return this.library.newLibFileToBeCreated();
	}
	
	public int getAnalysisState() {
		if (this.syncInProgress) {
			return LibSync.LIBSTATE_SYNCING;
		}
		else if (this.syncCompleted) {
			return LibSync.LIBSTATE_SYNC_OK;
		}
		else if (!this.mobileLibrary.fileAnalyzed() || !this.library.fileAnalyzed()) {
			return LibSync.LIBSTATE_ANALYZING;
		}
		else if (!this.itunesLibNotFound() && !this.mobileLibFileToBeCreated()) {
			// all ok
			return LibSync.LIBSTATE_BOTH_LIBS_OK;
		} else if (!this.itunesLibNotFound() && this.mobileLibFileToBeCreated()) {
			// new mobile lib
			return LibSync.LIBSTATE_ITUNES_LIB_OK_NO_MOBILE_LIB;
		} else if (this.itunesLibNotFound() && this.mobileLibFileToBeCreated()) {
			// no itunes or mobile lib found
			//System.out.println("Fubar");
			return LibSync.LIBSTATE_NO_ITUNES_LIB_NO_MOBILE_LIB;
		} else if (this.itunesLibNotFound() && !this.mobileLibFileToBeCreated()) {
			// no itunes lib found, mobile lib ok
			return LibSync.LIBSTATE_NO_ITUNES_LIB_MOBILE_LIB_OK;
		} else {
			return LibSync.LIBSTATE_ANALYZING;
		}
	}

	/**
	 * Copies tracks given in tracks parameter to another destination
	 * @param tracks a map of tracks with track id as key and track location as value 
	 * @throws IOException if filenames are incorrect
	 */
	private void copyTracks(Map<Integer,Song> tracks) throws IOException {
		for (int id : tracks.keySet()) {
			String filename = tracks.get(id).getFilename();
			++this.alreadySynced;
			FileUtils.copyFile(tracks.get(id).getLocation(), this.getSetting(Settings.MOBILE_LIBRARY_DIR) + File.separator + filename);
		}
	}
	
	private void delTracks(Map<Integer,Song> tracks) {
		for (int id : tracks.keySet()) {
			String filename = tracks.get(id).getFilename();
			try {
				filename = URLDecoder.decode(filename,"UTF-8");
			} catch (UnsupportedEncodingException e) {
			}
			++this.alreadyDeleted;
			FileUtils.delFile(this.getSetting(Settings.MOBILE_LIBRARY_DIR) + File.separator + filename);
		}
	}

	/**

	 * Syncs Mobile device library with iTunes library
	 * @param lib iTunes Library
	 * @param mobilib Mobile phone library
	 * @throws IOException if filenames are incorrect
	 */
	public void syncMobile(ItunesLibrary lib, ItunesLibrary mobilib) throws IOException {
		
		this.resetProgressStats();
		this.setPlaylistsToBeSyncedData();
		
		this.syncInProgress = true;
		this.syncCompleted = false;
		
		// delete files that are no more in itunes
		Map<Integer,Song> dif2 = new HashMap<Integer,Song>();
		dif2.putAll(mobilib.getTracks());
		
		for (int id : lib.getSelectedTracks().keySet()) {
			dif2.remove(id);
		}
		
		this.totalToDelete = dif2.size();
		
		this.delTracks(dif2);
		
		// we only need to sync files that are missing in mobile device
		Map<Integer,Song> dif = new HashMap<Integer,Song>();
		dif.putAll(lib.getSelectedTracks());
		
		Map<Integer,Song> othertracks; 
		if (mobilib != null) {
			othertracks = mobilib.getTracks(); // no playlists for mobile 
		}
		else {
			othertracks = new HashMap<Integer,Song>();
		}
		
		for (int id : othertracks.keySet()) {
			dif.remove(id);
		}
		this.totalToSync = dif.size();
		
		this.copyTracks(dif);
		
		//generate xml playlist for mobile device
		BufferedWriter writer = new BufferedWriter(new FileWriter(new File(
				this.getSetting(Settings.MOBILE_LIBRARY_DIR) + File.separator + 
						this.getSetting(Settings.LIBRARY_FILENAME))));
		writer.write(lib.generateXMLPlaylist());
		writer.close();
		
		// delete all m3u playlists
		Map<String,String> oldm3uplaylists = this.mobileLibrary.generateM3UPlaylists();
		for (String p : oldm3uplaylists.keySet()) {
			p = FileUtils.getValidFilename(p);
			FileUtils.delFile(this.getSetting(Settings.MOBILE_LIBRARY_DIR)+ 
					File.separator +p + ".m3u");
		}
		
		
		// generate m3u playlists for s60 music player
		Map<String,String> m3uplaylists = this.library.generateM3UPlaylists();
		for (String pl : m3uplaylists.keySet()) {
			pl = FileUtils.getValidFilename(pl);
			BufferedWriter plwriter = new BufferedWriter(new FileWriter(new File(
					this.getSetting(Settings.MOBILE_LIBRARY_DIR)+ File.separator + 
					pl + ".m3u")));
			String track = m3uplaylists.get(pl);
			if (track != null) {
				plwriter.write(track);
			}
			plwriter.close();
		}
		this.syncInProgress = false;
		this.syncCompleted = true;
		
		//System.out.println("Sync OK!");
	}
	
	private synchronized void setPlaylistsToBeSyncedData() {
		this.library.setWholeLibrarySync(this.getSetting(Settings.SYNC_ENTIRE_LIBRARY).equals("yes"));
		
		for (String s : this.library.getPlaylists()) {
			boolean flag = "yes".equals(this.getSetting(Settings.SYNC_PLAYLIST_PREFIX + s));
			this.library.setPlaylistToBeSynced(s, flag);
		}
	}
	
	public void syncMobile() throws IOException {
		this.syncMobile(this.library, this.mobileLibrary);
	}
	
	public void setPlaylistEnabled(int lib, String playlist, boolean flag) {
		if (lib == MOBILE_LIB) {
			this.mobileLibrary.setPlaylistToBeSynced(playlist, flag);
		} else if (lib == ITUNES_LIB) {
			this.library.setPlaylistToBeSynced(playlist, flag);
			this.setSetting(Settings.SYNC_PLAYLIST_PREFIX + playlist, flag ? "yes" : "no");
		} else throw new IllegalArgumentException("Library id incorrect!");
		
		//System.out.println(this.getTotalNeededDiskSpace() + " - " + this.getFreeSpaceOnDevice());
	}
	
	public long getTotalNeededDiskSpace() {
		this.setPlaylistsToBeSyncedData();
		
		// size of files that are going to be deleted
		Map<Integer,Song> dif2 = Collections.synchronizedMap(new HashMap<Integer,Song>());
		dif2.putAll(this.mobileLibrary.getTracks());
		for (int id : this.library.getSelectedTracks().keySet()) {
			//System.out.println(dif2.get(id));
			dif2.remove(id);
		}
		
		long size = 0;
		for (int id : dif2.keySet()) {
			if (dif2.get(id) != null) {
				size -= dif2.get(id).getSize();
			}
		}
		
		Map<Integer,Song> dif = new HashMap<Integer,Song>();
		dif.putAll(this.library.getSelectedTracks());
		
		Map<Integer,Song> othertracks; 
		if (this.mobileLibrary != null) {
			othertracks = this.mobileLibrary.getTracks(); // no playlists for mobile 
		}
		else {
			othertracks = new HashMap<Integer,Song>();
		}
		
		for (int id : othertracks.keySet()) {
			dif.remove(id);
		}
		
		for (int id : dif.keySet()) {
			size += dif.get(id).getSize();
		}
		
		return size;
		
	}
	
	public int getProgress() {
		//System.out.println("Total: " + this.totalToDelete + " - " + this.totalToSync);
		//System.out.println("Done: " + this.alreadyDeleted + " - " + this.alreadySynced);
		if (this.totalToDelete == 0 && this.totalToSync == 0) {
			return 0;
		}
		else if (this.totalToDelete == 0) {
			return (int)Math.round(100.0*this.alreadySynced/this.totalToSync);
		}
		else if (this.totalToSync == 0) {
			return (int)Math.round(100.0*this.alreadyDeleted/this.totalToDelete);
		}
		return (int)Math.round(100.0*(1.0*this.alreadySynced/this.totalToSync));
	}
	
	public int[] getProgressAmounts() {
		int[] am = {this.alreadySynced,this.totalToSync,this.alreadyDeleted,this.totalToDelete};
		return am;
	}

	private void loadLibs() throws FileNotFoundException {
		Debug.println("Loading libs...");
		ActionListener al1 = null;
		ActionListener al2 = null;
		if (this.library != null) {
			al1 = this.library.getActionListener();
		}
		if (this.mobileLibrary != null) {
			al2 = this.mobileLibrary.getActionListener();
		}
		
		this.library = new ItunesLibrary(this.getSetting(Settings.ITUNES_LIBRARY),this);
		this.mobileLibrary = new ItunesLibrary(this.getSetting(Settings.MOBILE_LIBRARY_DIR)
				+ File.separator + this.getSetting(Settings.LIBRARY_FILENAME),this);
		this.library.setAnalysisCompleteListener(al1);
		this.mobileLibrary.setAnalysisCompleteListener(al2);
		Debug.println("Libs loaded");
	}
	
	public void setAnalysisReadyListener(ActionListener al) {
		this.analysisCompleteListener = al;
		ActionListener al2 = new ActionListener() {
			public void actionPerformed(ActionEvent ae) {
				if (LibSync.this.mobileLibrary.fileAnalyzed() &&
						LibSync.this.library.fileAnalyzed()) {
					//System.out.println("Both files analyzed");
					if (LibSync.this.analysisCompleteListener != null) {
						//System.out.println("Notifying listener");
						LibSync.this.analysisCompleteListener.actionPerformed(ae);
					}
				}
			}
		};
		
		this.library.setAnalysisCompleteListener(al2);
		this.mobileLibrary.setAnalysisCompleteListener(al2);
	}
}

