package jp.co.corecorp.s3pdf;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.RandomAccessFile;
import java.io.UnsupportedEncodingException;
import java.net.Socket;
import java.net.UnknownHostException;
import java.util.Queue;
import java.util.UUID;
import java.util.concurrent.LinkedBlockingQueue;

import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

import android.content.Context;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.telephony.TelephonyManager;
import android.util.Log;

class RecvProc extends Thread
{
	private Socket sock;
	Queue<JSONObject> queue;

	public RecvProc(Socket s) {
		sock = s;
		queue = new LinkedBlockingQueue<JSONObject>();
	}
	public boolean isConnected() {
		return sock != null;
	}
	public void run() {
		Log.d("S3con", "recive thread start");
		try {
			ByteArrayOutputStream buff = new ByteArrayOutputStream();
			byte[] chunk = new byte[256];
			int chunkIn=0;
			InputStream in = sock.getInputStream();
			while(true) {
				int rs = in.read(chunk, chunkIn, chunk.length-chunkIn);
				boolean foundEnd = false;
				if( rs > 0 ) {
					int bs = chunkIn + rs;
					int i;
					for( i = chunkIn; i <= bs-4; i++ ) {
						if( chunk[i] == '\r' && chunk[i+1] == '\n' && chunk[i+2] == '\r' && chunk[i+3] == '\n') {
							foundEnd = true;
							break;
						}
					}
					if( i > 0 ) {
						buff.write(chunk, 0, i);
						if( foundEnd ) {
							System.arraycopy(chunk, i+4, chunk, 0, bs-i-4);
						} else {
							System.arraycopy(chunk, i, chunk, 0, bs-i);
						}
					}
					if( foundEnd ) {
						String jssrc = buff.toString("UTF-8");
						try {
							synchronized(this) {
								JSONObject js = new JSONObject(jssrc);
								queue.add(js);
								Log.d("s3con", "recive");
								notify();
							}
						} catch (IllegalMonitorStateException e) {
						} catch (JSONException e) {
							Log.d("s3con", e.toString());
						}
						buff.reset();
					}
				}
			}
		} catch (IOException e) {
			sock = null;
			Log.d("s3con", e.toString());
			synchronized(this) {
				notifyAll();
			}
		}
		sock = null;
		Log.d("S3con", "recive thread end");
	}
	
	public JSONObject waitForRecive() {
		Log.d("s3con", "waitForRecive");
		JSONObject r=null;
		while((r=queue.poll())==null) {
			while(queue.isEmpty()) {
				try {
					synchronized (this) {
						wait(500);
					}
				} catch (InterruptedException e) {
					Log.d("s3con", e.toString());
					return null;
				}
				if( sock == null ) return null;
			}
		}
		Log.d("s3con", "waitForRecive end");
		return r;
	}
}

class S3ServerInfo {
	String	host;
	int		port;
	int		load;
	public S3ServerInfo(String h, int p, int l) {
		host = h;
		port = p;
		load = l;
	}
}

public class S3Connection {
	private S3Context s3ctx;
	
	Socket sock = null;
	private boolean loggedin;

	private String linkVersion;
	private String authNonce;
	private String authAlg;
	private String lastLogin;
	private String accountId;
	private S3ServerInfo[] serverInfo;	
	private RecvProc recvProc;
	
	public S3Connection(S3Context ctx) {
		s3ctx = ctx;
	}
	
	private void setDeviceInfo(JsonBuilder jb) throws JsonHierarchException {
		String uuid = s3ctx.getDeviceId();
		jb.addObjectProp("did");
		jb.value(uuid);
		jb.addObjectProp("os");
		jb.value("Android");
		String ver = android.os.Build.VERSION.RELEASE;
		int sdk = android.os.Build.VERSION.SDK_INT;
		jb.addObjectProp("ver");
		jb.value(String.format("%s (level-%d)", ver, sdk));
		String brand = android.os.Build.BRAND;
		String model = android.os.Build.MODEL;
		String product = android.os.Build.PRODUCT;
		jb.addObjectProp("model");
		jb.value(String.format("%s %s (%s)", brand, model, product));
		String abi = android.os.Build.CPU_ABI;
		jb.addObjectProp("abi");
		jb.value(abi);
		TelephonyManager tm = (TelephonyManager)s3ctx.getContext().getSystemService(Context.TELEPHONY_SERVICE);
		if( tm != null ) {
			jb.addObjectProp("ca");
			jb.value(tm.getNetworkOperatorName());
		}
	}

	
	private JSONObject command(ByteArrayOutputStream cmd) throws S3Exception {
		try {
			byte[] line = {'\r','\n'};
			OutputStream os = sock.getOutputStream();
			os.write(cmd.toByteArray());
			os.write(line);
		} catch (IOException e) {
			Log.d("s3con", e.toString());
			throw new S3Exception(s3ctx.getContext().getString(R.string.transferError));
		}
		JSONObject r = recvProc.waitForRecive();
		return r;
	}
	
	String getLastLoginDate() {
		return lastLogin;
	}
	String getAccountId() {
		return accountId;
	}
	
	public boolean isConnected() {
		return recvProc != null && recvProc.isConnected();
	}
	
	public synchronized void connect(String host, int port) throws S3Exception {
		try {
			sock = new Socket(host, port);
			recvProc = new RecvProc(sock);
			recvProc.start();

			Log.d("s3con", "connect wait");
			JSONObject js = recvProc.waitForRecive();
			Log.d("s3con", "connect wait done");
			if( js == null ) {
				Log.d("s3con", "connect js=null");
				throw new S3Exception(s3ctx.getContext().getString(R.string.serverError));
			}
			JSONArray va = js.getJSONArray("version");
			if( va == null || va.length() == 0 ) {
				disconnect();
				throw new S3Exception(s3ctx.getContext().getString(R.string.serverError));
			}
			int i;
			for( i = 0; i < va.length(); i++ ) {
				String v = va.getString(i);
				if( v.equals("1.0") ) {
					linkVersion = "1.0";
					break;
				}
			}
			if( linkVersion == null ) {
				Log.d("s3con", "version error");
				disconnect();
				throw new S3Exception(s3ctx.getContext().getString(R.string.versionError));
			}
			JSONObject auth = js.getJSONObject("auth");
			authNonce 	= auth.getString("nonce");
			authAlg		= auth.getString("algorithm");
			
			JSONArray ha = js.getJSONArray("server");
			if( ha == null || ha.length() == 0 ) {
				disconnect();
				throw new S3Exception(s3ctx.getContext().getString(R.string.serverError));
			}
			serverInfo = new S3ServerInfo[ha.length()];
			for( i = 0; i < ha.length(); i++ ) {
				JSONObject v = ha.getJSONObject(i);
				String shost = v.getString("host");
				int sport = v.getInt("port");
				int sload = v.getInt("load");
				serverInfo[i] = new S3ServerInfo(shost, sport, sload);
			}
		}
		catch (JSONException e) {
			Log.d("s3con", e.toString());
			disconnect();
			throw new S3Exception(s3ctx.getContext().getString(R.string.serverError));
		}
		catch (UnknownHostException e) {
			Log.d("s3con", e.toString());
			disconnect();
			throw new S3Exception(s3ctx.getContext().getString(R.string.connectError));
		}
		catch (IOException e) {
			Log.d("s3con", e.toString());
			disconnect();
			throw new S3Exception(s3ctx.getContext().getString(R.string.transferError));
		}		
	}
	
	public synchronized void disconnect() {
		if( sock != null ) {
			try {
				sock.close();
			} catch (IOException e) {
			}
			sock = null;
		}
		if( recvProc != null ) {
			recvProc = null;
		}
		loggedin	= false;
		linkVersion	= null;
		authNonce	= null;
		authAlg		= null;
		serverInfo	= null;
	}
	
	public boolean isLoggedin() {
		return loggedin;
	}
	
	public synchronized void login(String user, String passwd) throws S3Exception {
		Log.d("s3con", "do login");
		if( !isConnected() ) {
			throw new S3Exception(s3ctx.getContext().getString(R.string.notConnectionError));
		}
		ByteArrayOutputStream out = new ByteArrayOutputStream();
		OutputStreamWriter os;
		try {
			JsonBuilder jb = new JsonBuilder();
			String js;
			try {
				jb.beginObject();
					jb.addObjectProp("login");
					jb.beginObject();
						jb.addObjectProp("version");
						jb.value(linkVersion);
						jb.addObjectProp("uid");
						jb.value(user);
						jb.addObjectProp("device");
						jb.beginObject();
							setDeviceInfo(jb);
						jb.endObject();
						jb.addObjectProp("auth");
						jb.beginObject();
							jb.addObjectProp("response");
							jb.value(passwd);
							jb.addObjectProp("nonce");
							jb.value(authNonce);
							jb.addObjectProp("cnonce");
							jb.value(authNonce);
							jb.addObjectProp("algorithm");
							jb.value(authAlg);
						jb.endObject();
					jb.endObject();
				jb.endObject();
				js = jb.endBuild();
			} catch (JsonHierarchException e) {
				e.printStackTrace();
				throw new S3Exception(s3ctx.getContext().getString(R.string.systemError));
			} catch (JsonEmptyObjectException e) {
				e.printStackTrace();
				throw new S3Exception(s3ctx.getContext().getString(R.string.systemError));
			}
			
			os = new OutputStreamWriter(out, "UTF-8");
			os.write(js);
			os.write("\r\n");
			os.close();
			JSONObject r = command(out);
			if( r == null ) {
				throw new S3Exception(s3ctx.getContext().getString(R.string.disconnectError));
			}
			int rc = r.getInt("result");
			if( rc != 0 ) {
				disconnect();
				throw new S3Exception(s3ctx.getContext().getString(R.string.loginError));
			}
			accountId = r.getString("aid");
			lastLogin = r.getString("lastDate");
			loggedin  = true;
			
		} catch (UnsupportedEncodingException e) {
			e.printStackTrace();
			throw new S3Exception(s3ctx.getContext().getString(R.string.serverError));
		} catch (IOException e) {
			e.printStackTrace();
			throw new S3Exception(s3ctx.getContext().getString(R.string.serverError));
		} catch (JSONException e) {
			e.printStackTrace();
			throw new S3Exception(s3ctx.getContext().getString(R.string.serverError));
		}
	}

	public synchronized void register(String user, String passwd, String name1, String name2) throws S3Exception {
		if( !isConnected() ) {
			throw new S3Exception(s3ctx.getContext().getString(R.string.notConnectionError));
		}
		ByteArrayOutputStream out = new ByteArrayOutputStream();
		OutputStreamWriter os;
		try {
			JsonBuilder jb = new JsonBuilder();
			String js;
			try {
				jb.beginObject();
					jb.addObjectProp("register");
					jb.beginObject();
						jb.addObjectProp("version");
						jb.value(linkVersion);
						jb.addObjectProp("uid");
						jb.value(user);
						jb.addObjectProp("name1");
						jb.value(name1);
						jb.addObjectProp("name2");
						jb.value(name2);
						jb.addObjectProp("device");
						jb.beginObject();
							setDeviceInfo(jb);
						jb.endObject();
						jb.addObjectProp("auth");
						jb.beginObject();
							jb.addObjectProp("password");
							jb.value(passwd);
							jb.addObjectProp("nonce");
							jb.value(authNonce);
							jb.addObjectProp("cnonce");
							jb.value(authNonce);
							jb.addObjectProp("algorithm");
							jb.value(authAlg);
						jb.endObject();
					jb.endObject();
				jb.endObject();
				js = jb.endBuild();
			} catch (JsonHierarchException e) {
				e.printStackTrace();
				throw new S3Exception(s3ctx.getContext().getString(R.string.systemError));
			} catch (JsonEmptyObjectException e) {
				e.printStackTrace();
				throw new S3Exception(s3ctx.getContext().getString(R.string.systemError));
			}
			
			os = new OutputStreamWriter(out, "UTF-8");
			os.write(js);
			os.write("\r\n");
			os.close();

			JSONObject r = command(out);
			if( r == null ) {
				throw new S3Exception(s3ctx.getContext().getString(R.string.disconnectError));
			}
			int rc = r.getInt("result");
			if( rc != 0 ) {
				disconnect();
				throw new S3Exception(s3ctx.getContext().getString(R.string.loginError));
			}
			accountId = r.getString("aid");
			lastLogin = r.getString("lastDate");
			loggedin  = true;
			
		} catch (UnsupportedEncodingException e) {
			e.printStackTrace();
			throw new S3Exception(s3ctx.getContext().getString(R.string.systemError));
		} catch (IOException e) {
			e.printStackTrace();
			throw new S3Exception(s3ctx.getContext().getString(R.string.systemError));
		} catch (JSONException e) {
			e.printStackTrace();
			throw new S3Exception(s3ctx.getContext().getString(R.string.systemError));
		}
	}
	public void registerService(S3Application svc) {
	}
	public synchronized void assignService(S3Application svc) throws S3Exception {
	
	}

}
