package de.tudarmstadt.informatik.hostage.protocol;

import java.security.SecureRandom;
import java.util.ArrayList;
import java.util.List;

import android.content.SharedPreferences;
import android.preference.PreferenceManager;

import de.tudarmstadt.informatik.hostage.Hostage;
import de.tudarmstadt.informatik.hostage.R;
import de.tudarmstadt.informatik.hostage.commons.HelperUtils;
import de.tudarmstadt.informatik.hostage.protocol.smbutils.NBDSType;
import de.tudarmstadt.informatik.hostage.protocol.smbutils.NBNS;
import de.tudarmstadt.informatik.hostage.protocol.smbutils.NBNSType;
import de.tudarmstadt.informatik.hostage.protocol.smbutils.NMB;
import de.tudarmstadt.informatik.hostage.protocol.smbutils.SMBPacket;
import de.tudarmstadt.informatik.hostage.wrapper.Packet;

/**
 * SMB protocol. It can handle the following requests: Negotiate Protocol
 * Request, Session Setup AndX Request, Tree Connect AndX Request, NT Create
 * AndX Request, Bind, NetShareEnumAll, Close Request, Tree Disconnect Request,
 * Echo Request, Trans2 Request.
 *
 * @author Wulf Pfeiffer
 */
public class SMB implements Protocol {
	// message constants
	private static final byte SMB_COM_CLOSE = 0x04;
	private static final byte SMB_COM_TRANSACTION = 0x25;
	private static final byte SMB_COM_ECHO = 0x2B;
	private static final byte SMB_COM_TRANSACTION2 = 0x32;
	private static final byte SMB_COM_TREE_DISCONNECT = 0x71;
	private static final byte SMB_COM_NEGOTIATE = 0x72;
	private static final byte SMB_COM_SESSION_SETUP_ANDX = 0x73;
	private static final byte SMB_COM_TREE_CONNECT_ANDX = 0x75;
	private static final byte SMB_COM_NT_CREATE_ANDX = (byte) 0xA2;

	/**
	 * Denotes in which state the protocol is right now
	 */
	private STATE state = STATE.NONE;
	private byte[] lastMessage;
	private NMB nmb;

	// version stuff
	private String[][] possibleSmbVersions = {
			{ "Windows 7 Professional 7600", "Windows 7 Professional 6.1" },
			{ "Windows 8 Enterprise 9200", "Windows 8 Enterprise 9200" },
			{ "Windows Server 2008 R2 Enterprise 7600", "Windows Server 2008 R2 Enterprise 6.1" },
			{ "Windows Server 2012 Standard 6.2", "Windows Server 2012 Standard 6.2" },
			{ "Unix", "Samba" },
			{ "Windows 2002 Service Pack 2", "Windows 2002 5.1" }
	};

	/**
	 * Represents the states of the protocol
	 */
	private static enum STATE {
		NONE, CONNECTED, AUTHENTICATED, LISTING, DISCONNECTED, CLOSED
	}

	public void setIP(String ip) {
//		TODO if porthack is working for UDP uncomment
        nmb = new NMB(ip, "BLIBLABLUB", "Workgroup");
        nmb.start();

//		nmb = new NMB(ip, new String(serverName), workgroup);
//		nmb.start();
	}

	private String[] initServerVersion() {
		String sharedPreferencePath = Hostage.getContext().getString(
				R.string.shared_preference_path);
		String profile = Hostage
				.getContext()
				.getSharedPreferences(sharedPreferencePath,
						Hostage.MODE_PRIVATE).getString("os", "");
		if(profile.equals("Windows XP")) {
			workgroup = "MSHOME";
		} else {
			workgroup = "WORKGROUP";
		}

		if (profile.equals("Windows 7")) {
			return possibleSmbVersions[0];
		} else if (profile.equals("Windows 8")) {
			return possibleSmbVersions[1];
		} else if (profile.equals("Windows Server 2008")) {
			return possibleSmbVersions[2];
		} else if (profile.equals("Windows Server 2012")) {
			return possibleSmbVersions[3];
		} else if (profile.equals("Linux")) {
			return possibleSmbVersions[4];
		} else if (profile.equals("Windows XP")) {
			return possibleSmbVersions[5];
		} else {
			return possibleSmbVersions[new SecureRandom().nextInt(possibleSmbVersions.length)];
		}
	}

	//required to be declared down here, do not change position over initServerVersion() and possibleServerVersions!!
	private String[] serverVersion = initServerVersion();
	private static byte[] serverName = HelperUtils.fillWithZero(HelperUtils
			.getRandomString(16, true).getBytes());
	private static String workgroup;
	private SMBPacket smbPacket = new SMBPacket(serverVersion, new String(serverName), workgroup);
	private int maxEchoPackets = initMaxPackets();
	private int receivedEchoPackets = 0;
	
	private int initMaxPackets() {
		int maxPackets;
		SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(Hostage.getContext());
		maxPackets = prefs.getInt("timeout", 30) / 5;
		return maxPackets;
	}

	@Override
	public int getPort() {
		return 445;
	}

	@Override
	public boolean isClosed() {
		return (state == STATE.CLOSED);
	}

	@Override
	public boolean isSecure() {
		return false;
	}

    //just for debugging purpose
    final protected static char[] hexArray = "0123456789ABCDEF".toCharArray();
    public static String bytesToHex(byte[] bytes) {
        char[] hexChars = new char[bytes.length * 2];
        for ( int j = 0; j < bytes.length; j++ ) {
            int v = bytes[j] & 0xFF;
            hexChars[j * 2] = hexArray[v >>> 4];
            hexChars[j * 2 + 1] = hexArray[v & 0x0F];
        }
        return new String(hexChars);
    }

	@Override
	public List<Packet> processMessage(Packet requestPacket) {
        if (requestPacket != null && requestPacket.getBytes().length != 0){
            lastMessage = requestPacket.getBytes();
            System.out.println("request packet " + bytesToHex(lastMessage));
            smbPacket.prepareNextResponse(lastMessage);
        }
        else{
            //something needs to be done, if there is no message
            smbPacket.prepareNextResponse(0);
        }
		byte smbCommand = smbPacket.getSmbCommand();
		byte[] response;
		List<Packet> responsePackets = new ArrayList<Packet>();

		if (smbCommand == SMB_COM_ECHO) {
			receivedEchoPackets++;
		} else {
			receivedEchoPackets = 0;
		}
		
		if (receivedEchoPackets == maxEchoPackets) {
			state = STATE.CLOSED;
			response = smbPacket.getTreeDisc();
			responsePackets.add(new Packet(response, toString()));
			return responsePackets;
		}
		
		switch (state) {
			case NONE:
				if (smbCommand == SMB_COM_NEGOTIATE) {
					state = STATE.CONNECTED;
					response = smbPacket.getNego();
				} else {
					state = STATE.DISCONNECTED;
					response = smbPacket.getTreeDisc();
				}
				break;
			case CONNECTED:
				if (smbCommand == SMB_COM_SESSION_SETUP_ANDX) {
					response = smbPacket.getSessSetup();
				} else if (smbCommand == SMB_COM_TREE_CONNECT_ANDX) {
					state = STATE.AUTHENTICATED;
					response = smbPacket.getTreeCon();
				} else {
					state = STATE.DISCONNECTED;
					response = smbPacket.getTreeDisc();
				}
				break;
			case AUTHENTICATED:
				if (smbCommand == SMB_COM_NT_CREATE_ANDX) {
					state = STATE.LISTING;
					response = smbPacket.getNTCreate();
				} else if (smbCommand == SMB_COM_ECHO) {
					response = smbPacket.getEcho();
				} else if (smbCommand == SMB_COM_TRANSACTION2) {
					response = smbPacket.getTrans2();
				} else if (smbCommand == SMB_COM_CLOSE) {
					response = smbPacket.getClose();
				} else if (smbCommand == SMB_COM_TREE_DISCONNECT) {
					state = STATE.CLOSED;
					response = smbPacket.getTreeDisc();
				} else {
					state = STATE.DISCONNECTED;
					response = smbPacket.getTreeDisc();
				}
				break;
			case LISTING:
				if (smbCommand == SMB_COM_TRANSACTION) {
					response = smbPacket.getTrans();
				} else if (smbCommand == SMB_COM_CLOSE) {
					response = smbPacket.getClose();
				} else if (smbCommand == SMB_COM_TREE_DISCONNECT) {
					state = STATE.CLOSED;
					response = smbPacket.getTreeDisc();
				} else if (smbCommand == SMB_COM_NEGOTIATE) {
					state = STATE.CONNECTED;
					response = smbPacket.getNego();
				} else {
					state = STATE.DISCONNECTED;
					response = smbPacket.getTreeDisc();
				}
				break;
			case DISCONNECTED:
				state = STATE.CLOSED;
				response = smbPacket.getTreeDisc();
				break;
			default:
				state = STATE.CLOSED;
				response = smbPacket.getTreeDisc();
		}
		responsePackets.add(new Packet(response, toString()));
		return responsePackets;
	}

	@Override
	public String toString() {
		return "SMB";
	}

	@Override
	public TALK_FIRST whoTalksFirst() {
		return TALK_FIRST.CLIENT;
	}
}