/* NFC Spy is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 3 of the License, or
(at your option) any later version.

NFC Spy is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with Wget.  If not, see <http://www.gnu.org/licenses/>.

Additional permission under GNU GPL version 3 section 7 */

package com.sinpo.nfcspy;

import static com.sinpo.nfcspy.ServiceFactory.MSG_P2P_CONNECT;
import static com.sinpo.nfcspy.ServiceFactory.MSG_P2P_DISCONN;
import static com.sinpo.nfcspy.ServiceFactory.STA_FAIL;
import static com.sinpo.nfcspy.ServiceFactory.STA_P2P_ACCEPT;
import static com.sinpo.nfcspy.ServiceFactory.STA_P2P_CLIENT;
import static com.sinpo.nfcspy.ServiceFactory.STA_SUCCESS;
import static com.sinpo.nfcspy.ThisApplication.PRIORITY_HIGH;
import static com.sinpo.nfcspy.ThisApplication.setCurrentThreadPriority;

import java.io.BufferedOutputStream;
import java.io.Closeable;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketException;

final class WiFiP2PSocket extends Thread {
	static final int BUF_SIZE = 512;

	ServerSocket server;
	Socket client;

	private ServiceFactory.SpyCallback callback;
	private DataInputStream iStream;
	private DataOutputStream oStream;

	WiFiP2PSocket(ServiceFactory.SpyCallback callback) {
		this.callback = callback;
	}

	synchronized boolean isConnected() {

		if (client == null)
			return false;

		return (server != null) ? server.isBound() : client.isConnected();
	}

	boolean sendData(int type, byte[] data) {
		if (data != null && data.length > 0 && oStream != null) {

			try {
				oStream.writeInt(type);
				oStream.writeInt(data.length);
				oStream.write(data);
				oStream.flush();
				return true;
			} catch (Exception e) {
				return false;
			}
		}
		return false;
	}

	synchronized void close() {
		iStream = null;
		oStream = null;
		try {
			if (client != null && !client.isClosed())
				client.close();
		} catch (Throwable e) {
		}
		client = null;

		try {
			if (server != null && !server.isClosed())
				server.close();
		} catch (Throwable e) {
		}
		server = null;
	}

	@Override
	public void run() {
		synchronized (this) {
			notifyAll();
		}

		setCurrentThreadPriority(PRIORITY_HIGH);

		try {
			iStream = new DataInputStream(client.getInputStream());
			oStream = new DataOutputStream(new BufferedOutputStream(
					client.getOutputStream(), BUF_SIZE));

			while (true) {

				int type = iStream.readInt();
				int len = iStream.readInt();

				byte[] data = new byte[len];

				iStream.readFully(data);

				callback.handleMessage(type, STA_SUCCESS, data);
			}
		} catch (Exception e) {
		} finally {
			close();
			callback.handleMessage(MSG_P2P_DISCONN, STA_SUCCESS, null);
		}
	}
}

final class SocketConnector extends Thread {

	private final ServiceFactory.SpyCallback callback;
	final WiFiP2PManager context;

	SocketConnector(WiFiP2PManager ctx, ServiceFactory.SpyCallback cb) {
		callback = cb;
		context = ctx;
	}

	@Override
	public void run() {
		final WiFiP2PManager ctx = context;

		ctx.closeSocket();

		WiFiP2PSocket p2p = new WiFiP2PSocket(callback);
		ctx.p2pSocket = p2p;

		Socket client = null;
		ServerSocket server = null;

		try {

			if (ctx.isGroupOwner) {
				fireCallback(STA_P2P_ACCEPT);

				server = new ServerSocket(WiFiP2PManager.PORT);
				p2p.server = server;

				client = server.accept();
				p2p.client = client;
			} else {
				fireCallback(STA_P2P_CLIENT);

				server = null;
				p2p.server = server;

				client = new Socket();
				p2p.client = client;

				client.bind(null);
				client.connect(new InetSocketAddress(ctx.groupOwnerAddress,
						WiFiP2PManager.PORT), 8000);
			}

		} catch (Exception e) {

			safeClose(client);
			client = null;
			p2p.client = null;

			safeClose(server);
			p2p.server = null;

			ctx.p2pSocket = null;
		}

		if (client != null && client.isConnected()) {

			try {
				tuneupSocket(client);

				p2p.start();

				synchronized (p2p) {
					p2p.wait();
				}

				fireCallback(STA_SUCCESS);
			} catch (Exception e) {
				fireCallback(STA_FAIL);
			}

		} else {
			fireCallback(STA_FAIL);
		}
	}

	protected void fireCallback(int sta) {
		callback.handleMessage(MSG_P2P_CONNECT, sta, null);
	}

	private static void tuneupSocket(Socket skt) throws SocketException {
		skt.setTcpNoDelay(true);
		skt.setTrafficClass(0x04 | 0x10);

		if (skt.getSendBufferSize() < WiFiP2PSocket.BUF_SIZE)
			skt.setSendBufferSize(WiFiP2PSocket.BUF_SIZE);

		if (skt.getReceiveBufferSize() < WiFiP2PSocket.BUF_SIZE)
			skt.setReceiveBufferSize(WiFiP2PSocket.BUF_SIZE);
	}

	private static void safeClose(Closeable obj) {
		try {
			if (obj != null)
				obj.close();
		} catch (Throwable e) {
		}
	}
}