package hh.dejsem.net;

import android.content.Context;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;

import java.io.File;
import java.net.InetSocketAddress;

import hh.dejsem.K;
import hh.dejsem.Prefs;
import hh.dejsem.fm.EntitySize;
import hh.dejsem.fm.FSLocal;
import hh.dejsem.fm.FSServer;
import hh.lib.D;

/**
 * - base class of data operations over network SSL connection
 * - all methods are called from subprocess of main and may perform net opers
 */
public abstract class NetConn {
	D d;
	int port = -1;
	NetData netData;
	public NetSslScIO netIO = null;
	FSLocal localFS;
	public FSServer remoteFS;
	FileIO fio;
	FileInfo fileInfo;
	File file = null;
	// ----------------------------------------------------------

	static ConnectivityManager cm = null;

	public static boolean netAvailable(D d) {
		boolean avail = false;
		if(cm == null) cm = (ConnectivityManager)d.getContext().getSystemService(Context.CONNECTIVITY_SERVICE);
		NetworkInfo activeNetworkInfo = cm.getActiveNetworkInfo();
		if(activeNetworkInfo != null) avail = activeNetworkInfo.getState() == NetworkInfo.State.CONNECTED;
		if(!avail) d.lmwa("network not available");
		return avail;
	}

	public static boolean lanAvailable(D d) {
		if(!netAvailable(d)) return false;
		if(cm.getActiveNetworkInfo().getType() == ConnectivityManager.TYPE_WIFI) return true;
		d.lmwa("LAN access not available");
		return false;
	}

	NetConn(D d) throws Exception {
		this.d = d.klon(this);
		fileInfo = new FileInfo(d);
		netIO = new NetSslScIO(d);
		localFS = new FSLocal(this.d);
		remoteFS = new FSServer(this.d);
	}

	void connectSock(InetSocketAddress hostIp) throws Exception {
		String host = hostIp.getHostName();
		int port = hostIp.getPort();
		if(d.ll(3)) d.l(String.format("%sSSL, connecting to %s:%d...", (Prefs.ssl ? "" : "non"), host, port));
		try { netIO.connect(hostIp); } catch(Exception e) { abend(e); }

		boolean accepted = false;
		NetData confirm = new NetData(d, 8);
		while(confirm.buf.remaining() > 0) netIO.get(confirm.buf);
		confirm.unloadToString();
		if(d.ll(3)) d.l(confirm.data + " received as confirm");

		if(confirm.data.equals("ACCEPTED")) accepted = true;
		if(!accepted) abend(false, "connection rejected by server", null);
		if(d.ll(3)) d.l("connected to " + host + ":" + port);
	}

	void accountRec(EntitySize es, int delta, String fn) {
		if(d.ll(5)) d.l(String.format("EntitySize counter=%s, fn=%s, incremented by %d, target size=%d, running size=%d, pct=%d",
				es.hashCode(), fn, delta, es.getTargetSize(), es.getRunningSize(), es.getPct()));
		es.incrR(delta, fn); }

	void accountFile(EntitySize es, long d) { es.incrF(); }

	void accountDir(EntitySize es, String dn) { es.incrD(dn); }

	public void endOfData() throws Exception { if(netIO.out != null) putNum(0); }

	String getCmd() throws Exception {
		NetData cmd = new NetData(d, 8);
		netIO.get(cmd.buf);
		cmd.unloadToString();
		return cmd.data;
	}

	void putAttrs(EntitySize targetSize) throws Exception { new FileInfo(d, targetSize).put(); }

	void putAttrs(File f, String fp) throws Exception { new FileInfo(d, f, fp).put(); }

	void putCmd(String act) throws Exception {
		netIO.put(new NetData(d, act).buf);
		if(d.ll(4)) d.l(act + " cmd send");
	}

	public void batchEnd() throws Exception { if(netIO.out != null) putCmd(K.LongTaskAction.BATCHEND.action); }

	public String getStr() throws Exception { return NetChOps.getStr(d, netIO); }

	void putStr(String s) throws Exception { NetChOps.putStr(d, s, netIO); }

	public long getNum() throws Exception { return NetChOps.getNum(d, netIO); }

	void putNum(long n) throws Exception { NetChOps.putNum(d, n, netIO); }

	void abend(Exception x) throws Exception {  // by default hlasitý ABEND
		abend(false, x);
	}

	void abend(boolean quiet, Exception x) throws Exception {
		synchronized(K.app) {
			K.cmdConnClose(d);
			connClose();
			if(quiet) throw new K.End(x);
			else throw x;
		}
	}

	void abend(boolean quiet, String msg, Exception x) throws Exception {   // ABEND s doprovodnou informací
		synchronized(K.app) {
			K.cmdConnClose(d);
			connClose();
			final String m = getClass().getName() + "[" + msg + "]";
			if(quiet) throw new K.End(m, x);
			else throw new Exception(m, x);
		}
	}

	abstract void connClose();

	/**
	 * holds information about file and communicates it over data stream
	 */
	class FileInfo {
		D d;
		String fn;
		long size;
		long timestamp;

		FileInfo(D d) { this.d = d.klon(this); }

		FileInfo(D d, File realFile, File relFile) { this(d, realFile, relFile.getPath()); }

		FileInfo(D d, File file, String fn) {
			this.d = d.klon(this);
			this.fn = fn;
			size = file.isFile() ? file.length() : -1;
			timestamp = (long)file.lastModified()/1000;
		}

		FileInfo(D d, EntitySize es) {
			this.d = d.klon(this);
			this.fn = "dummy fn for batch size";
			size = es.getTargetSize();
			timestamp = 0L;
		}

		boolean get() throws Exception { return get(netIO); }

		boolean get(GetPut io) throws Exception {
			fn = NetChOps.getStr(d, io);
			if(fn == null || fn.length() == 0) return false;
			else {
				fn = fn.replaceAll(K.FN_RESERVED_CHARS_REGEX, ".");
				size = NetChOps.getNum(d, io);
				timestamp = NetChOps.getNum(d, io);
				if(d.ll(4)) d.l("fname=" + fn + ", size=" + size + ", timestamp=" + timestamp);
				return true; }
		}

		void put() throws Exception { put(netIO); }

		void put(GetPut io) throws Exception {
			NetChOps.putStr(d, fn, io);
			NetChOps.putNum(d, size, io);
			NetChOps.putNum(d, timestamp, io);
		}
	}
}
