/*
 * Result callback for the listAccountsTemplate
 *
 * result list form:
 * {
		"templateId": "com.palm.aol",
		"loc_name": "AOL",
		"validator": "palm://com.palm.imaccountvalidator/checkCredentials",
		"capabilityProviders": [
			{
				"capability": "MESSAGING",
				"capabilitySubtype": "IM",
				"id": "com.palm.aol.aim",
				"loc_name": "AIM",
				"loc_shortName": "AIM",
				"icon": {
					"loc_32x32": "/usr/palm/public/accounts/com.palm.aol/images/aim32x32.png",
					"loc_48x48": "/usr/palm/public/accounts/com.palm.aol/images/aim48x48.png",
					"splitter": "/usr/palm/public/accounts/com.palm.aol/images/aim_transport_splitter.png"
				},
				"implementation": "palm://com.palm.imlibpurple/",
				"onEnabled": "palm://com.palm.imlibpurple/onEnabled",
				"serviceName": "type_aim",
				"dbkinds": {
					"immessage": "com.palm.immessage.libpurple",
					"imcommand": "com.palm.imcommand.libpurple"
				}
			}
		]
	},
 */
MojErr SendOneMessageHandler::listAccountResult(MojObject& result, MojErr err)
{
	if (err) {
		MojString error;
		MojErrToString(err, error);
		MojLogError(IMServiceApp::s_log, _T("listAccountTemplates failed. error %d - %s"), err, error.data());
		// not much we can do here...don't leave message pending
		failMessage(ERROR_SEND_GENERIC_ERROR);

	}
	else {
		IMServiceHandler::logMojObjectJsonString(_T("listAccountTemplates success: %s"), result);

		// if the flag is not there, assume we can send to a non buddy
		// if we got here, the message is going to a non buddy
		bool nonBuddyChatOK = true;

		// find the template and provider for our service and look for "chatWithNonBuddies":false
		// "results" in result
		MojObject results;
		result.get(_T("results"), results);

		// check to see if array is empty - should not be...
		if (!results.empty()){

			// find the messaging capability object
			MojObject capability;
			MojObject accountTemplate;
			MojString serviceName;
			bool found = false;
			bool foundService = false;

			MojObject::ConstArrayIterator templItr = results.arrayBegin();
			while (templItr != results.arrayEnd()) {
				accountTemplate = *templItr;
				IMServiceHandler::logMojObjectJsonString(_T("listAccountTemplates template: %s"), accountTemplate);

				// now find the capabilityProviders array
				MojObject providersArray;
				found = accountTemplate.get(XPORT_CAPABILITY_PROVIDERS, providersArray);
				if (found) {
					MojObject::ConstArrayIterator capItr = providersArray.arrayBegin();
					while (capItr != providersArray.arrayEnd()) {
						capability = *capItr;
						IMServiceHandler::logMojObjectJsonString(_T("listAccountTemplates capability: %s"), capability);

						// find the one for our service
						capability.get(XPORT_SERVICE_TYPE, serviceName, found);
						if (found) {
							MojLogInfo(IMServiceApp::s_log, _T("listAccountTemplates - capability service %s. "), serviceName.data());
							if (0 == serviceName.compare(m_serviceName)) {
								foundService = true;
								found = capability.get("chatWithNonBuddies", nonBuddyChatOK);
								MojLogInfo(IMServiceApp::s_log, _T("listAccountTemplates - found service %s. found %d, chatWithNonBuddies %d"), m_serviceName.data(), found, nonBuddyChatOK);
								break;
							}
						}
						else MojLogError(IMServiceApp::s_log, _T("error: no service name in capability provider"));
						capItr++;
					}
				}
				else MojLogError(IMServiceApp::s_log, _T("error: no provider array in templateId"));
				if (foundService)
					break;
				templItr++;
			}
		}
		if (nonBuddyChatOK){
			// send to the transport
			sendToTransport();
		}
		else {
			// permanent error - no retry option
			MojLogError(IMServiceApp::s_log, _T("error: Trying to send to user that is not on buddy list"));
			failMessage(ERROR_SEND_TO_NON_BUDDY);
		}
	}

	return MojErrNone;
}
/*
 * Send one IMCommand that has handler set to transport
 *
 * errors returned up to OutgoingIMCommandHandler
 *
 *
 * @param imCmd - imCommand that was read out of the DB with handler set to transport
 {
   "id"      : "ImCommand",
   "type"    : "object",
   "properties" : {
      "command"       : {"type"        : "string",
                         "enum"        : ["blockBuddy","deleteBuddy","sendBuddyInvite","receivedBuddyInvite","createChatGroup","inviteToGroup","leaveGroup"],
                         "description" : "The command to be processed"},
      "params"        : {"type"        : "any",
                         "description" : "Parameters associated with the command are stored here."}
      "handler"       : {"type"        : "string",
                         "enum"        : ["transport","application"],
                         "description" : "Who is responsible for handling the command"},
      "targetUsername"  : {"type"        : "string",
                         "description" : "The buddyname the command will act on"},
      "fromUsername"  : {"type"        : "string",
                         "description" : "The username that originated the command"},
      "serviceName"   : {"type"        : "string",
                         "description" : "Name of originating service (see IMAddress of contacts load library*)."}
      }
 * }
 */
MojErr SendOneCommandHandler::doSend(const MojObject imCmd) {

	IMServiceHandler::logMojObjectJsonString(_T("send command: %s"),imCmd);
	MojString command;
	LibpurpleAdapter::SendResult retVal = LibpurpleAdapter::SENT;
	bool found = false;

	MojErr err = imCmd.get(MOJDB_COMMAND, command, found);
	MojErrCheck(err);

	// serviceName
	err = imCmd.get(MOJDB_SERVICE_NAME, m_serviceName, found);
	MojErrCheck(err);

	// get the id so we can update the status in  the DB after sending
	err = imCmd.getRequired(MOJDB_ID, m_currentCmdDbId);
	MojErrCheck(err);

	// for receiving invite, the user account and remote user are reversed: targetUsername is us and the fromUsername is the remote buddy
	if (0 == command.compare(_T("receivedBuddyInvite"))) {
		// username of current account
		err = imCmd.get(MOJDB_FROM_USER, m_buddyName, found);
		MojErrCheck(err);

		// buddy / remote user
		err = imCmd.get(MOJODB_TARGET_USER, m_username, found);
		MojErrCheck(err);
	}
	else {
		// username of current account
		err = imCmd.get(MOJDB_FROM_USER, m_username, found);
		MojErrCheck(err);

		// buddy / remote user
		err = imCmd.get(MOJODB_TARGET_USER, m_buddyName, found);
		MojErrCheck(err);
	}

	// which command?
	if (0 == command.compare(_T("blockBuddy"))) {
		retVal = blockBuddy(imCmd);
	}
	else if (0 == command.compare(_T("deleteBuddy"))) {
		retVal = removeBuddy(imCmd);
	}
	else if (0 == command.compare(_T("sendBuddyInvite"))) {
		retVal = inviteBuddy(imCmd);
	}
	else if (0 == command.compare(_T("receivedBuddyInvite"))) {
		retVal = receivedBuddyInvite(imCmd);
	}
	else {
		MojLogError(IMServiceApp::s_log, _T("doSend: unknown command %s"), command.data());
		retVal = LibpurpleAdapter::SEND_FAILED;
	}

	// we can't just delete the command if the user is not logged on...
	// need to save command in "waiting" state waiting for user to login
	if (LibpurpleAdapter::USER_NOT_LOGGED_IN == retVal) {
		// user not logged in - put in queued state
		MojLogError(IMServiceApp::s_log, _T("doSend - can't process command - user not logged in. Waiting for connection"));
		MojObject propObject;
		propObject.putString(MOJDB_STATUS, IMMessage::statusStrings[WaitingForConnection]);

		// id to update
		propObject.putString(MOJDB_ID, m_currentCmdDbId);

		// save the new fields - call merge
		err = m_dbClient.merge(this->m_imSaveCommandSlot, propObject);
		if (err) {
			MojLogError(IMServiceApp::s_log, _T("doSend - DB merge command failed. err %d, DB id %s: "), err, m_currentCmdDbId.data() );
		}
	}
	else {
		// delete command so we don't keep processing it
		// put id in an array
		MojObject idsToDelete;  // array
		err = idsToDelete.push(m_currentCmdDbId);

		// luna://com.palm.db/del '{"ids":[2]}'
		IMServiceHandler::logMojObjectJsonString(_T("deleting imcommand: %s"), idsToDelete);
		err = m_dbClient.del(this->m_imDeleteCommandSlot, idsToDelete.arrayBegin(), idsToDelete.arrayEnd());

		if (err) {
			MojLogError(IMServiceApp::s_log, _T("doSend - DB del command failed. err %d, DB id %s: "), err, m_currentCmdDbId.data() );
		}
	}

	if (LibpurpleAdapter::SENT != retVal) {
		// something went wrong - nothing more we can do
		MojLogError(IMServiceApp::s_log, _T("doSend: command failed"));
		m_outgoingIMHandler->messageFinished();

	}

	return MojErrNone;
}
MojErr OnEnabledHandler::findImLoginStateResult(MojObject& payload, MojErr err)
{
	MojLogTrace(IMServiceApp::s_log);

	if (err != MojErrNone) {
		MojString error;
		MojErrToString(err, error);
		MojLogCritical(IMServiceApp::s_log, _T("findImLoginStateResult failed: error %d - %s"), err, error.data());
	}
	else {
		// "results" in result
		MojObject results;
		payload.get(_T("results"), results);

		// check to see if array is empty - normally it will be if this is a newly created account. There should never be more than 1 item here
		if (!results.empty()){

			IMServiceHandler::logMojObjectJsonString(_T("findImLoginStateResult found existing imLoginState record: %s"), payload);

			// if there is a record already, make sure the account id matches.
			MojObject loginState;
			MojObject::ConstArrayIterator itr = results.arrayBegin();
			bool foundOne = false;
			while (itr != results.arrayEnd()) {
				if (foundOne) {
					MojLogError(IMServiceApp::s_log,
							_T("findImLoginStateResult: found more than one ImLoginState with same username/serviceName - using the first one"));
					break;
				}
				loginState = *itr;
				foundOne = true;
				itr++;
			}

			MojString accountId;
			MojErr err = loginState.getRequired("accountId", accountId);
			if (err) {
				MojLogError(IMServiceApp::s_log, _T("findImLoginStateResult: missing accountId in loginState entry"));
			}
			if (0 != accountId.compare(m_accountId)) {
				MojLogError(IMServiceApp::s_log, _T("findImLoginStateResult: existing loginState record does not have matching account id. accountId = %s"), accountId.data());

				// delete this record
				MojObject idsToDelete;
				MojString dbId;
				err = loginState.getRequired("_id", dbId);
				if (err) {
					MojLogError(IMServiceApp::s_log, _T("findImLoginStateResult: missing dbId in loginState entry"));
				}
				else {
				    idsToDelete.push(dbId);

					// luna://com.palm.db/del '{"ids":[2]}'
					MojLogInfo(IMServiceApp::s_log, _T("findImLoginStateResult: deleting loginState entry id: %s"), dbId.data());
					err = m_dbClient.del(this->m_deleteImLoginStateSlot, idsToDelete.arrayBegin(), idsToDelete.arrayEnd());
					if (err != MojErrNone) {
						MojString error;
						MojErrToString(err, error);
						MojLogError(IMServiceApp::s_log, _T("findImLoginStateResult: db.del() failed: error %d - %s"), err, error.data());
					}
				}
			}
			// if the account id matches, leave the old record and we are done
			else return MojErrNone;
		}

		// no existing record found or the old one was deleted - create a new one
		MojLogInfo(IMServiceApp::s_log, _T("findImLoginStateResult: no matching loginState record found for %s, %s. creating a new one"), m_username.data(), m_serviceName.data());
		MojObject imLoginState;
		imLoginState.putString(_T("_kind"), IM_LOGINSTATE_KIND);
		imLoginState.put(_T("accountId"), m_accountId);
		imLoginState.put(_T("serviceName"), m_serviceName);
		imLoginState.put(_T("username"), m_username);
        imLoginState.put(_T("capabilityId"), m_capabilityProviderId);
		imLoginState.putString(_T("state"), LOGIN_STATE_OFFLINE);
		imLoginState.putInt(_T("availability"), PalmAvailability::ONLINE); //default to online so we automatically login at first
        imLoginState.put(_T("config"), m_config);
		MojErr err = m_dbClient.put(m_addImLoginStateSlot, imLoginState);
		if (err != MojErrNone) {
			MojString error;
			MojErrToString(err, error);
			MojLogError(IMServiceApp::s_log, _T("findImLoginStateResult: db.put() failed: error %d - %s"), err, error.data());
		}

	}
	return err;
}
//TODO: when the connection is lost (no internet), complete/cancel the activity and exit the service
MojErr ConnectionState::ConnectionStateHandler::connectionManagerResult(MojObject& result, MojErr err)
{
	// log the parameters
	IMServiceHandler::logMojObjectJsonString(_T("ConnectionStateHandler::connectionManagerResult: %s"), result);

	if (err == MojErrNone && result.contains("$activity"))
	{
		MojObject activity, requirements, internetRequirements;
		// The connectionmanager status is under $activity.requirements.internet
		err = result.getRequired("$activity", activity);
		if (err == MojErrNone)
		{
			err = activity.getRequired("requirements", requirements);
			if (err == MojErrNone)
			{
				err = requirements.getRequired("internet", internetRequirements);
			}
		}

		if (err == MojErrNone)
		{
			m_receivedResponse = true;
			bool prevInetConnected = m_connState->m_inetConnected;
			internetRequirements.get("isInternetConnectionAvailable", m_connState->m_inetConnected);

			bool prevWifiConnected = m_connState->m_wifiConnected;
			bool found = false;
			MojObject wifiObj;
			err = internetRequirements.getRequired("wifi", wifiObj);
			MojString wifiState;
			err = wifiObj.getRequired("state", wifiState);
			m_connState->m_wifiConnected = (err == MojErrNone && wifiState.compare("connected") == 0);
			// If the connection was lost, keep the old ipAddress so it can be used to
			// notify accounts that were using it
			if (m_connState->m_wifiConnected == true)
			{
				err = wifiObj.get("ipAddress", m_connState->m_wifiIpAddress, found);
				if (err != MojErrNone || found == false)
				{
					// It may claim to be connected, but there's no interface IP address
					MojLogError(IMServiceApp::s_log, _T("Marking WiFi as disconnected because ipAddress is missing"));
					m_connState->m_wifiConnected = false;
				}
				MojLogInfo(IMServiceApp::s_log, _T("ConnectionStateHandler::connectionManagerResult found=%i, wifi ipAddress=%s"), found, m_connState->m_wifiIpAddress.data());
			}

			bool prevWanConnected = m_connState->m_wanConnected;

			MojObject wanObj;
			err = internetRequirements.getRequired("wan", wanObj);
			MojErrCheck(err);

			MojString wanState;
			err = wanObj.getRequired("state", wanState);
			MojErrCheck(err);

			m_connState->m_wanConnected = (err == MojErrNone && wanState.compare("connected") == 0);
			// If the connection was lost, keep the old ipAddress so it can be used to
			// notify accounts that were using it
			if (m_connState->m_wanConnected == true)
			{
				err = wanObj.get("ipAddress", m_connState->m_wanIpAddress, found);
				if (err != MojErrNone || found == false)
				{
					// It may claim to be connected, but there's no interface IP address
					MojLogError(IMServiceApp::s_log, _T("Marking WAN as disconnected because ipAddress is missing"));
					m_connState->m_wanConnected = false;
				}
				MojLogInfo(IMServiceApp::s_log, _T("ConnectionStateHandler::connectionManagerResult found=%i, wan ipAddress=%s"), found, m_connState->m_wanIpAddress.data());
			}

			// If the loginState machine setup a listener, then post the change
			if (m_loginState != NULL)
			{
				if (prevInetConnected != m_connState->m_inetConnected ||
					prevWifiConnected != m_connState->m_wifiConnected ||
					prevWanConnected != m_connState->m_wanConnected)
				{
					MojLogInfo(IMServiceApp::s_log, _T("ConnectionStateHandler::connectionManagerResult - connection changed - scheduling activity."));
					ConnectionState::ConnectionChangedScheduler *changeScheduler = new ConnectionState::ConnectionChangedScheduler(m_service);
					changeScheduler->scheduleActivity();
				}
				else {
					MojLogInfo(IMServiceApp::s_log, _T("ConnectionStateHandler::connectionManagerResult - no change from previous state."));
				}
			}
		}
	}
	else {
		MojLogInfo(IMServiceApp::s_log, _T("ConnectionStateHandler::connectionManagerResult no activity object - ignoring. err = %d"), err);
	}
	return MojErrNone;
}