package hh.dejsem.fm;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Map;
import java.util.TreeMap;

import hh.dejsem.K;
import hh.dejsem.Prefs;
import hh.dejsem.net.NetChOps;
import hh.dejsem.net.FileIO;
import hh.dejsem.net.NetConn;
import hh.dejsem.net.SrvCmd;
import hh.lib.D;

/**
 * list of files in local directory
 */
class LocalFileList extends FileList {

	LocalFileList(D d, String ncwd) { super(d, ncwd); }

	@Override
	void collectFileAttrs() throws Exception {
		Prefs.sp.edit().putString(K.CACHED_LOC_CWD_KEY, dirName).apply();
		File dir = new File(dirName);
		int len = ( dir.listFiles() != null ? dir.listFiles().length : 0 );
		if(len > 0)
			for(File f: dir.listFiles()) {
				long size = (f.isFile() ? f.length() : -1);
				long date = f.lastModified();
				put(f.getName(), new FileAttrs(f.getName(), size, date)); }
	}
}

/**
 * list of files in server directory
 */
class RemoteFileList extends FileList {

	RemoteFileList(D d, String ncwd) { super(d, ncwd); }

	@Override
	void collectFileAttrs() throws Exception {
		if(!NetConn.netAvailable(d)) return;
		String fn;
		String cached = FragFM.getCachedCwdName(d, K.cacheFileR);
		if(cached == null || !cached.equals(dirName)) {
			if(d.ll(4)) d.l(4, "no cache for " + dirName + ", creating...");
			if(K.cacheFileR.exists()) K.cacheFileR.delete();		// cache používám jako random access, tak proto delete
			try { SrvCmd.PULLLIST.exec(d, new String[] {dirName, K.cacheFileR.getPath()}); }
			catch(K.EntryNotFound e) {}
			FragFM.sweepOutNetCmd(d);
		}
		if(d.ll(4)) d.l("cache file size=" + K.cacheFileR.length() + ", cache fn=" + K.cacheFileR.getPath());
		try {
			FileIO fio = new FileIO(d, K.cacheFileR, "r");
			int i = 12 + NetChOps.getStr(d, fio).length();		// skip cwdFl name
			while((fn = NetChOps.getStr(d, fio)) != null && fn.length() > 0) {
				long size = NetChOps.getNum(d, fio);
				long date = NetChOps.getNum(d, fio)*1000;		// msecs
				put(fn, new FileAttrs(fn, size, date));
				fn = null; }
			fio.close();
		}
		catch(IOException e) { throw new Exception("file attributes from cache", e); }
	}
}

/**
 * seznam souborů ve formě TreeMap<String, FileAttrs> a operace na něm
 * <p>
 *     ● instance drží ještě
 *     <ul>
 *     <li>directory name
 *     <li>seznam file names jako String[]
 *     <li>aktuální pozici v seznamu
 *     <li>ListView adapter
 *     </ul>
 **/
abstract class FileList extends TreeMap<String, FileAttrs> {

	D d;
	String dirName = "";
	String[] ls = new String[] {};
	int listPos = 0;
	FileListAdapter listAdapter = null;

	/**
	 * vrací celkovou velikost souborů v lokálním FS zadaných seznamem jmen
	 *
	 * @param selection String[]: seznam file names
	 * @return EntitySize
	 * @throws Exception the exception
	 */
	static EntitySize reckon(String[] selection) throws Exception {
		ArrayList<File> fa = new ArrayList<File>(selection.length);
		for(String fp: selection) fa.add(new File(fp));
		File[] fsel = new File[fa.size()];
		return reckonRecurse(fa.toArray(fsel));
	}

	static EntitySize reckonRecurse(File[] selection) throws Exception {
		EntitySize ds = new EntitySize();
		for(File f: selection) {
			if(f.isFile()) { ds.addTargetFile(f.length()); }
			else {
				ds.addTargetDir();
				// za nějakých okolností prázdný dir vrátí null místo prázdného pole (možná podle permissions, nezkoumal jsem)
				final File[] fl = f.listFiles();
				if(fl != null) ds.add(reckonRecurse(fl));
			}
		}
		return ds;
	}

	FileList(D d) {
		super();
		this.d = d.klon(this); }

	FileList(D d, String cwd) {
		this(d);
		dirName = cwd;
		populateFileList();
	}

	@Override
	public String toString() { return super.toString() + "[" + dirName + "]"; }

	abstract void collectFileAttrs() throws Exception;

	/**
	 * obnoví file list z file systému se zachováním selection
	 */
	void populateFileList() {
		final String[] sel = getStrArrSelection();
		clear();
		try { collectFileAttrs(); }
		catch(Exception e) {
			d.abendMsg("dir list", e);
			clear(); }
		setStrArrSelection(sel);
		populateLs();
	}

	/**
	 * vrací uloženou pozici
	 *
	 * @return int
	 */
	int getCurrPos() { return listPos; }

	/**
	 * cachuje pozici ve file listu
	 *
	 * @param pos   int: pozice
	 */
	void setCurrPos(int pos) { listPos = pos; }

	/**
	 * vrací atributy soubory se jménem fn
	 *
	 * @param fn Strig: file name
	 * @return FileAttrs
	 */
	FileAttrs getFa(String fn) { return get(fn); }

	/**
	 * vrací file list view adapte (případně ho alokuje)
	 *
	 * @return FileListAdapter
	 */
	FileListAdapter getListAdapter() {
		if(listAdapter == null) listAdapter = new FileListAdapter(d, this);
		return listAdapter;
	}

	boolean nameExists(String fn) { return containsKey(fn); }

	/**
	 * aktualizuje file names array
	 */
	void populateLs() {
		ls = new String[size()];
		if(ls.length > 0) {
			String fn = firstKey();
			for(int i = 0; i < ls.length; i++) {
				ls[i] = fn;
				if(d.ll(5)) d.l("ls[" + i + "]=" + ls[i]);
				if(higherKey(fn) != null) fn = higherKey(fn); } }
	}

	/**
	 * gets fn by file list position
	 *
	 * @param pos int: position in file list
	 * @return String file name
	 */
	String getFnByPos(int pos) { return ls[pos]; }

	/**
	 * get file names of selected files
	 *
	 * @return string array of fullpaths of selected files
	 */
	String[] getStrArrSelection() {
		HashSet<String> chkSet = new HashSet<String>(size());
		for(Map.Entry<String, FileAttrs> e: entrySet())
			if(e.getValue().isChecked()) chkSet.add(new File(dirName, e.getKey()).getPath());
		String[] selection = new String[chkSet.size()];
		chkSet.toArray(selection);
		if(d.ll(4)) d.l("" + selection.length + " items preselected");
		return selection;
	}

	/**
	 * get selected files
	 *
	 * @return File array
	 */
	File[] getArrSelection() {
		HashSet<File> chkSet = new HashSet<File>(size());
		for(Map.Entry<String, FileAttrs> e: entrySet())
			if(e.getValue().isChecked()) chkSet.add(new File(dirName, e.getKey()));
		File[] selection = new File[chkSet.size()];
		chkSet.toArray(selection);
		if(d.ll(4)) d.l("" + selection.length + " items preselected");
		return selection;
	}

	/**
	 * get file array of all files in file list
	 *
	 * @return File array
	 */
	File[] getAll() {
		HashSet<File> fSet = new HashSet<File>(size());
		for(String fn: keySet()) fSet.add(new File(dirName, fn));
		File[] all = new File[fSet.size()];
		fSet.toArray(all);
		return all;
	}

	/**
	 * select files in file list according to string array of file names
	 *
	 * @param selection the selection
	 */
	void setStrArrSelection(String[] selection) {
		for(String path: selection) {
			final String fn = path.substring(path.lastIndexOf('/') + 1);    // basename
			if(get(fn) != null) put(fn, get(fn).setChecked(true));
		}
	}

	/**
	 * select/unselect file on position in file list
	 *
	 * @param pos     int: position in file list
	 * @param checked boolean: true=select
	 */
	void setSelection(int pos, boolean checked) { setStrSelection(getFnByPos(pos), checked); }

	/**
	 * select/unselect file on with file name fn
	 *
	 * @param fn      String: file name of file
	 * @param checked boolean: true=select
	 */
	void setStrSelection(String fn, boolean checked) { put(fn, get(fn).setChecked(checked)); }

	/**
	 * select all files in file list
	 */
	void selectAll() { for(String fn: keySet()) setStrSelection(fn, true); }

	/**
	 * unselect all files in file list
	 */
	void clearSelected() { for(String fn: keySet()) setStrSelection(fn, false);  }
}

