public class AppController {
    private static final String LOG = AppController.class.getSimpleName();

    private static AppController mInstance;

    private AppCompatActivity mMainScreenActivity = null;

    private boolean mIsBackended = false;

    private static Activity mCurrentActivityContext = null;

    private TabContacts mContactsTab;
    private TabChats mChatsTab;

    private DatabaseHelper mDatabaseHelper;
    private TorProcessManager mTorProcessManager;
    private TextView mTorStatusCarrier;

    private boolean isFirstRun = true;

    private long mBundlePid;
    private String mMyAddress;

    private HistoryCleanupManager mHistoryCleanupManager;

    private AppController(AppCompatActivity mainActivity) {
        mMainScreenActivity = mainActivity;
//        binding the textView to the MainScreen
        mTorStatusCarrier = new TextView(mMainScreenActivity.getApplicationContext());

        initBackend();
    }

    /**
     * To be used with non-null Context the first time this object is created in the most early app time
     * In rest to be used with null Context parameter
     */
    public static synchronized AppController getInstanceParameterized(AppCompatActivity mainActivity) {
        if (mInstance == null) {
            mInstance = new AppController(mainActivity);
        }
        return mInstance;
    }

    /**
     * binding the backend functionality to MainScreen context
     */
    private void initBackend() {
        Log.i(LOG, "initBackend -> ENTER");

//        Database init
        mDatabaseHelper = new DatabaseHelper(mMainScreenActivity.getApplicationContext(), PersistenceConstants.DATABASE_ANONYMITY_CHAT,
                null, PersistenceConstants.DATABASE_VERSION);
        mDatabaseHelper.initOperations();
        mDatabaseHelper.triggerInsertDefaultMyProfile();
        Log.i(LOG, "init -> mDatabaseHelper - DONE");

        mHistoryCleanupManager = new HistoryCleanupManager();

        final AppController appController = this;
        mHistoryCleanupManager.init(appController);
        Log.i(LOG, "init -> mHistoryCleanupManager - DONE");

        mBundlePid = mDatabaseHelper.getMyProfileOperations().getBundlePid(1);
        Log.i(LOG, "bundlePidLong=" + mBundlePid);

//        handling the previous instance of the app which left the bundle process open
        if (mBundlePid > 0) {
            Log.i(LOG, "the previous bundle process needs to be cleaned: mBundlePid=" + mBundlePid);

            Log.e(LOG, "Killing the previous TorProcess");
            android.os.Process.killProcess((int) getBundlePid());
            Log.e(LOG, "succeded. Now updating the current PID");

            updateBundlePid(0);
        }

        mTorProcessManager = new TorProcessManager(mMainScreenActivity, mTorStatusCarrier);
//        TODO: activate automatic TOR connection
//        Automatic TOR connection start
//        final String networkStatus = mTorStatusCarrier.getText().toString().trim();
//
//        if (!mTorProcessManager.isTorBundleStarted() && (networkStatus.equals(TorConstants.TOR_BUNDLE_STOPPED))) {
//            mTorProcessManager.startTorBundle();
//
//            detectFirstRun();
//        }

        Log.i(LOG, "init -> mTorProcessManager - DONE");

// initializing the ChatController and implicitly the TorReceiver
        mMyAddress = getMyProfile().getMyAddress();
        handleChatController();
        Log.i(LOG, "init -> ChatController - DONE");

        Log.i(LOG, "initBackend -> LEAVE");
    }

    public void startNetworkConnection() {
        Log.i(LOG, "startNetworkConnection -> ENTER");

        final String networkStatus = mTorStatusCarrier.getText().toString().trim();

        if (!mTorProcessManager.isTorBundleStarted() && (networkStatus.equals(TorConstants.TOR_BUNDLE_STOPPED))) {
            mTorProcessManager.startTorBundle();

            detectFirstRun();
        }

        Log.i(LOG, "startNetworkConnection -> LEAVE");
    }

    public void stopHistoryCleanupJob() {
        Log.i(LOG, "stopHistoryCleanupJob -> ENTER");

        mHistoryCleanupManager.stopAllCleanupJobs();

        Log.i(LOG, "stopHistoryCleanupJob -> LEAVE");
    }

    public void stopNetworkConnection() {
        Log.i(LOG, "stopNetworkConnection -> ENTER");

        mTorProcessManager.stopTorBundle();

        Log.i(LOG, "stopNetworkConnection -> LEAVE");
    }

    private void detectFirstRun() {
        Log.i(LOG, "detectFirstRun -> ENTER");

        mTorStatusCarrier.addTextChangedListener(new TextWatcher() {
            @Override
            public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {

            }

            @Override
            public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {
                Log.i(LOG, "detectFirstRun.onTextChanged -> LEAVE charSequence=" + charSequence);

                final StringBuilder sb = new StringBuilder(charSequence.length());
                sb.append(charSequence);
                String textViewText = sb.toString();

                if (isFirstRun) {
                    if (textViewText.equals(TorConstants.TOR_BUNDLE_STARTED)) {
                        handleFirstRun();
                        isFirstRun = false;

                        mMyAddress = getMyProfile().getMyAddress();
                    }
                }
                if (textViewText.equals(TorConstants.TOR_BUNDLE_STARTED)) {
//                    handleChatController();
                    ChatController.getInstance().setMyAddress(mMyAddress);
                }

//                if (textViewText.equals(TorConstants.TOR_BUNDLE_STOPPED)) {
//                    Log.i(LOG, "tor bundle has stopped ... clearing Chat Controller resources");
//                    // ChatController managed resources --- what is to be set to default
//
//                    ChatController.cleanUp();
//                }

                Log.i(LOG, "detectFirstRun.onTextChanged -> LEAVE");
            }

            @Override
            public void afterTextChanged(Editable editable) {

            }
        });

        Log.i(LOG, "detectFirstRun -> LEAVE");
    }

    private boolean handleFirstRun() {
        Log.i(LOG, "firstRunCheck -> ENTER");
        boolean retVal = false;

        if (mTorProcessManager.isTorBundleStarted()) {

            String myAddress = mTorProcessManager.getTorAddress().substring(0, 16);

            if (myAddress.length() == AppConstants.ADDRESS_SIZE) {

                mDatabaseHelper.getMyProfileOperations().updateAddressOnFirstNetworkConnection(myAddress);

                ChatController.getInstance().setMyAddress(mMyAddress);

                retVal = true;
            }
        }

        Log.i(LOG, "firstRunCheck -> LEAVE retVal=" + retVal);
        return retVal;
    }

    private void handleChatController() {
        Log.i(LOG, "handleChatController -> ENTER");

        ChatController.getInstance();
        ChatController.getInstance().setMyAddress(mMyAddress);
        ChatController.getInstance().setCurrentActivityContext(mMainScreenActivity);
        ChatController.getInstance().initializeChatConnectionManagement();

        ChatController.getInstance().setCurrentActivityContext(mMainScreenActivity);

        Log.i(LOG, "handleChatController -> LEAVE");
    }

    public void updateBundlePid(long newPid) {
        Log.i(LOG, "updateBundlePid -> ENTER newPid=" + newPid);

        mBundlePid = newPid;

        long currentPid = mDatabaseHelper.getMyProfileOperations().getBundlePid(1);

        if (currentPid != newPid) {
            mDatabaseHelper.getMyProfileOperations().updateBundlePid(newPid);
        }

        Log.i(LOG, "updateBundlePid -> LEAVE");
    }

    public long getBundlePid() {
        Log.i(LOG, "getBundlePid -> ENTER");

        Log.i(LOG, "getBundlePid -> LEAVE mBundlePid=" + mBundlePid);
        return mBundlePid;
    }

    public void setContactsTab(TabContacts contactsTab) {
        Log.i(LOG, "setContactsTab -> ENTER");
        mContactsTab = contactsTab;
        Log.i(LOG, "setContactsTab -> LEAVE");
    }

    public void addNewContactToTab(ContactEntity newContactEntity) {
        Log.i(LOG, "addNewContactToTab -> ENTER");
        mContactsTab.addContactToList(newContactEntity);
        Log.i(LOG, "addNewContactToTab -> LEAVE");
    }

    public void removeContactFromTab(ContactEntity newContactEntity) {
        Log.i(LOG, "removeContactFromTab -> ENTER");
        mContactsTab.removeContactFromList(newContactEntity);
        Log.i(LOG, "removeContactFromTab -> LEAVE");
    }

    public void setChatsTab(TabChats chatsTab) {
        Log.i(LOG, "setChatsTab -> ENTER");
        mChatsTab = chatsTab;
        Log.i(LOG, "setChatsTab -> LEAVE");
    }

//    private void addNewChatToTab(ChatEntity newChatEntity) {
//        Log.i(LOG, "addNewChatToTab -> ENTER newChatEntity=" + newChatEntity);
//        mChatsTab.addChatToList(newChatEntity);
//        Log.i(LOG, "addNewChatToTab -> LEAVE");
//    }

    public void editChatToTab(ChatEntity chatEntity) {
        Log.i(LOG, "editChatToTab -> ENTER chatEntity=" + chatEntity);
        mChatsTab.updateContactList(chatEntity);
        Log.i(LOG, "editChatToTab -> LEAVE");
    }

    public void editContactToTab(ContactEntity contactEntity) {
        Log.i(LOG, "editContactToTab -> ENTER contactEntity=" + contactEntity);
        mContactsTab.updateContactList(contactEntity);
        Log.i(LOG, "editContactToTab -> LEAVE");
    }

    public void addMessageToChatHistory(DBChatMessageEntity chatMessageEntity) {
        Log.i(LOG, "addMessageToChatHistory -> ENTER chatMessageEntity=" + chatMessageEntity);
        mDatabaseHelper.getChatMessagesOperations().addDbEntity(chatMessageEntity);
        Log.i(LOG, "addMessageToChatHistory -> LEAVE");
    }

    public List<IDbEntity> getMessageHistoryForSessionId(long contactSessionId) {
        Log.i(LOG, "getMessageHistory -> ENTER contactSessionId=" + contactSessionId);
        List<IDbEntity> retVal;

        retVal = mDatabaseHelper.getChatMessagesOperations().getAllMessagesForSessionId(contactSessionId);

        Log.i(LOG, "getMessageHistory -> LEAVE retVal=" + retVal + ", retVal.size()=" + retVal.size());
        return retVal;
    }

    public void removeMessagesForChat(long contactSessionId) {
        Log.i(LOG, "removeMessagesForChat -> ENTER contactSessionId=" + contactSessionId);
        mDatabaseHelper.getChatMessagesOperations().deleteAllMessagesForSessionId(contactSessionId);
        Log.i(LOG, "removeMessagesForChat -> LEAVE");
    }

//    public void moveToChatsTab() {
//        Log.i(LOG, "moveToChatsTab -> ENTER");
//        ((MainScreenActivity) mMainScreenActivity).moveToChatsTab();
//        Log.i(LOG, "moveToChatsTab -> LEAVE");
//    }

    public void setChatControllerCurrentActivityContext(Activity context) {
        Log.i(LOG, "setChatControllerCurrentActivityContext -> ENTER context=" + context);
        ChatController.getInstance().setCurrentActivityContext(context);
        mCurrentActivityContext = context;
        Log.i(LOG, "setChatControllerCurrentActivityContext -> LEAVE");
    }

    public Activity getCurrentActivityContext() {
        return mCurrentActivityContext;
    }

    public void updateWithNetworkConnectionStatus(final TextView networkConnStatusLabel) {
        Log.i(LOG, "getNetworkConnectionStatus -> ENTER networkConnStatusLabel=" + networkConnStatusLabel);

        mTorStatusCarrier.addTextChangedListener(new TextWatcher() {
            @Override
            public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {

            }

            @Override
            public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {
                networkConnStatusLabel.setText(mTorStatusCarrier.getText());
            }

            @Override
            public void afterTextChanged(Editable editable) {

            }
        });

        Log.i(LOG, "getNetworkConnectionStatus -> LEAVE");
    }

    public String getNetworkConnectionStatus() {
        Log.i(LOG, "getNetworkConnectionStatus -> ENTER");

        String retVal = mTorStatusCarrier.getText().toString();

        Log.i(LOG, "getNetworkConnectionStatus -> LEAVE retVal=" + retVal);
        return retVal;
    }

    public void triggerNotification(String partnerAddress) {
        Log.i(LOG, "triggerNotification -> ENTER");

        if (mIsBackended) {
            IDbEntity iDbEntityByAddress = mDatabaseHelper.getContactsOperations().getIDbEntityByAddress(partnerAddress);

            String title = "";

            if (iDbEntityByAddress != null) {
                DBContactEntity dbContactEntity = (DBContactEntity) iDbEntityByAddress;
                title = dbContactEntity.getName() + " contacted you!";
            } else {
                title = partnerAddress.substring(0, 16) + " contacted you!";
            }

            ((MainScreenActivity) mMainScreenActivity).triggerNotification(title, "Incoming chat request from your contacts!");

        }

        Log.i(LOG, "triggerNotification -> LEAVE");
    }

    public void setCurrentActivityContext(Activity context) {
        Log.i(LOG, "setCurrentActivityContext -> ENTER context=" + context);

        mCurrentActivityContext = context;

        Log.i(LOG, "setCurrentActivityContext -> LEAVE");
    }

    //    TODO: all DB interaction
    public void openDB(SQLiteDatabase sqLiteDatabase) {
        Log.i(LOG, "openDB -> ENTER");

        mDatabaseHelper.onOpen(sqLiteDatabase);

        Log.i(LOG, "openDB -> LEAVE");
    }

    public void closeDB() {
        Log.i(LOG, "closeDB -> ENTER");

        mDatabaseHelper.close();

        Log.i(LOG, "closeDB -> LEAVE");
    }

    public DBMyProfileEntity getMyProfile() {
        Log.i(LOG, "getMyProfile -> ENTER");
        DBMyProfileEntity retVal = null;

//        retVal = mDatabaseHelper.getMyProfileOperations().getAllIDbEntity().isEmpty() ?
//                null : (DBMyProfileEntity) mDatabaseHelper.getMyProfileOperations().getAllIDbEntity().get(0);

        retVal = (DBMyProfileEntity) mDatabaseHelper.getMyProfileOperations().getAllIDbEntity().get(0);

        Log.i(LOG, "getMyProfile -> LEAVE retVal=" + retVal);
        return retVal;
    }

    public void updateMyProfile(DBMyProfileEntity myProfileEntity) {
        Log.i(LOG, "updateMyProfile -> ENTER");
        mDatabaseHelper.getMyProfileOperations().updateDbEntity(myProfileEntity);
        Log.i(LOG, "updateMyProfile -> LEAVE");
    }

    public boolean addNewContact(DBContactEntity contactEntity) {
        Log.i(LOG, "addNewContact -> ENTER contactEntity=" + contactEntity);
        boolean retVal;

        final DBContactEntity contact = (DBContactEntity) mDatabaseHelper.getContactsOperations().getIDbEntityByAddress(contactEntity.getAddress());

        if (contact == null) {
            contactEntity.setSessionId(generateSessionId());
            mDatabaseHelper.getContactsOperations().addDbEntity(contactEntity);

            addNewChatForContact(contactEntity);

            retVal = true;

        } else {
            retVal = false;
        }

        Log.i(LOG, "addNewContact -> LEAVE retVal=" + retVal);
        return retVal;
    }

    public boolean updateContact(DBContactEntity contactEntity) {
        Log.i(LOG, "addNewContact -> ENTER contactEntity=" + contactEntity);
        boolean retVal;

//        final DBContactEntity contact = (DBContactEntity) mDatabaseHelper.getContactsOperations().getIDbEntityByAddress(contactEntity.getAddress());

        final int updateSuccesfull = mDatabaseHelper.getContactsOperations().updateDbEntity(contactEntity);

        if (updateSuccesfull > 0) {
            retVal = true;
        } else {
            retVal = false;
        }

        Log.i(LOG, "addNewContact -> LEAVE retVal=" + retVal);
        return retVal;
    }

    public boolean deleteContact(String contactAddress) {
        Log.i(LOG, "deleteContact -> ENTER contactAddress=" + contactAddress);
        boolean retVal = false;

        final DBContactEntity contact = (DBContactEntity) mDatabaseHelper.getContactsOperations().getIDbEntityByAddress(contactAddress);

        // if the contact to be updated is found
        if (contact != null) {
            mDatabaseHelper.getChatMessagesOperations().deleteAllMessagesForSessionId(contact.getSessionId());
            Log.i(LOG, "all messages deleted");

            DBChatEntity chatEntity = new DBChatEntity();
            chatEntity.setSessionId(contact.getSessionId());
            boolean chatDeleted = mDatabaseHelper.getChatsOperations().deleteDbEntity(chatEntity);

            if (chatDeleted) {
                Log.i(LOG, "chat deleted -> ");

                retVal = mDatabaseHelper.getContactsOperations().deleteDbEntity(contact);
                Log.i(LOG, "contact deleted -> ");

                mHistoryCleanupManager.removeHistoryCleanupJob(chatEntity.getSessionId());
                Log.i(LOG, "history cleanup job deleted -> ");

                ContactEntity contactEntity = new ContactEntity(contact.getName(), contact.getSessionId());
                removeContactFromTab(contactEntity);
            }
        }

        Log.i(LOG, "deleteContact -> LEAVE retVal=" + retVal);
        return retVal;
    }

    public DBContactEntity getContactEntity(long sessionId) {
        Log.i(LOG, "getContactEntity -> ENTER sessionId=" + sessionId);
        DBContactEntity retVal = null;

        retVal = (DBContactEntity) mDatabaseHelper.getContactsOperations().getIDbEntityById(sessionId);

        Log.i(LOG, "getContactEntity -> LEAVE retVal=" + retVal);
        return retVal;
    }

    public DBContactEntity getContactEntity(String address) {
        Log.i(LOG, "getContactEntity -> ENTER address=" + address);
        DBContactEntity retVal = null;

        retVal = (DBContactEntity) mDatabaseHelper.getContactsOperations().getIDbEntityByAddress(address);

        Log.i(LOG, "getContactEntity -> LEAVE retVal=" + retVal);
        return retVal;
    }

    private boolean addNewChatForContact(DBContactEntity contactEntity) {
        Log.i(LOG, "addNewChatForContact -> ENTER contactEntity=" + contactEntity);
        boolean retVal;

        DBChatEntity chatEntity = new DBChatEntity();
        chatEntity.setSessionId(contactEntity.getSessionId());
        chatEntity.setChatName(contactEntity.getAddress());
        chatEntity.setHistoryCleanupTime(0);

        retVal = mDatabaseHelper.getChatsOperations().addDbEntity(chatEntity);

        mHistoryCleanupManager.addHistoryCleanupJob(chatEntity.getSessionId(), chatEntity.getHistoryCleanupTime());


//        if (retVal) {
////            TODO: fix chat availability
//            ChatEntity tabChatEntity = new ChatEntity(chatEntity.getSessionId(), chatEntity.getChatName(), false);
//            addNewChatToTab(tabChatEntity);
//        }

        Log.i(LOG, "addNewChatForContact -> LEAVE retVal=" + retVal);
        return retVal;
    }

    public DBChatEntity getChatEntity(long sessionId) {
        Log.i(LOG, "getChatEntity -> ENTER sessionId=" + sessionId);
        DBChatEntity retVal = null;

        retVal = (DBChatEntity) mDatabaseHelper.getChatsOperations().getIDbEntityById(sessionId);

        Log.i(LOG, "getChatEntity -> LEAVE retVal=" + retVal);
        return retVal;
    }

//    public DBChatEntity getChatEntity(String address) {
//        Log.i(LOG, "getChatEntity -> ENTER address=" + address);
//        DBChatEntity retVal = null;
//
//        retVal = (DBChatEntity) mDatabaseHelper.getChatsOperations().getIDbEntityById(sessionId);
//
//        Log.i(LOG, "getChatEntity -> LEAVE retVal=" + retVal);
//        return retVal;
//    }

    public boolean updateChat(DBChatEntity chatEntity) {
        Log.i(LOG, "updateChat -> ENTER chatEntity=" + chatEntity);
        boolean retVal = false;

        if (chatEntity != null) {

            mDatabaseHelper.getChatsOperations().updateDbEntity(chatEntity);

            mHistoryCleanupManager.updateHistoryCleanupJob(chatEntity.getSessionId(), chatEntity.getHistoryCleanupTime());

            retVal = true;
        }

        Log.i(LOG, "updateChat -> LEAVE retVal=" + retVal);
        return retVal;
    }

    public List<IDbEntity> getChatList() {
        Log.i(LOG, "getChatList -> ENTER");
        List<IDbEntity> retVal = new ArrayList();

        retVal = mDatabaseHelper.getChatsOperations().getAllIDbEntity();

        Log.i(LOG, "getChatList -> LEAVE retVal=" + retVal);
        return retVal;
    }

    public List<IDbEntity> getContactList() {
        Log.i(LOG, "getContactList -> ENTER");
        List<IDbEntity> retVal = new ArrayList();

        retVal = mDatabaseHelper.getContactsOperations().getAllIDbEntity();

        Log.i(LOG, "getContactList -> LEAVE retVal=" + retVal);
        return retVal;
    }

    private long generateSessionId() {
        Log.i(LOG, "generateSessionId -> ENTER");

        long retVal = UUID.randomUUID().getMostSignificantBits();

        Log.i(LOG, "generateSessionId -> LEAVE retVal=" + retVal);
        return retVal;
    }

    public void setIsBackended(boolean isBackended) {
        Log.i(LOG, "setIsBackended -> ENTER isBackended=" + isBackended);
        mIsBackended = isBackended;
        Log.i(LOG, "setIsBackended -> LEAVE");
    }

//    TODO create fontAwesome loading method here
//    a generic FA loading method used throughout the app
} public interface ChatModelConstants {

    public static final SimpleDateFormat SIMPLE_DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss", Locale.getDefault());

    public static final String MESSAGE_EOL = System.getProperty("line.separator");

    public static final String CHAT_ACTIVITY_INTENT_EXTRA_KEY = "SESSION_ID";

    public static final long DEFAULT_SESSION_ID = 0L;

    public static final String MY_TOR_ADDRESS_NA_YET = "Not available yet !";

    ////////////
    //PROTOCOL//
    ///////////

    public static final String FIRST_CHAT_MESSAGE = "->START<-";
    public static final String MESSAGE_END_CHAT = "->END<-";

} public class ChatActivityManagerImpl implements IChatActivityManager, IMessageReceivedListener {

    private static final String CHAT_ACTIVITY_MANAGER_TAG = ChatActivityManagerImpl.class.getSimpleName();

    private ChatActivity mChatActivity = null;

    private ChatDetail mChatDetail = null;

    private long mSessionId = 0L;

    public ChatActivityManagerImpl() {
        Log.i(CHAT_ACTIVITY_MANAGER_TAG, "ChatActivityManagerImpl -> ENTER");

        Log.i(CHAT_ACTIVITY_MANAGER_TAG, "ChatActivityManagerImpl -> LEAVE");
    }

    @Override
    public void onCreate() {
        Log.i(CHAT_ACTIVITY_MANAGER_TAG, "onCreate -> ENTER");

        if (AppController.getInstanceParameterized(null).getNetworkConnectionStatus().equals(TorConstants.TOR_BUNDLE_STARTED)) {
            //Preparing TorConnection with partner
            mChatDetail = ChatController.getInstance().establishConnectionToPartner(this, mSessionId);
        }
        //TODO get ChatDetail and if isAlive is False means Offline Mode
        //If the connection could not be established means that the partner is offline

        Log.i(CHAT_ACTIVITY_MANAGER_TAG, "onCreate -> LEAVE");
    }

    @Override
    public void onPause() {
        Log.i(CHAT_ACTIVITY_MANAGER_TAG, "onPause -> ENTER");
        Log.i(CHAT_ACTIVITY_MANAGER_TAG, "onPause -> LEAVE");
    }

    @Override
    public void onResume() {
        Log.i(CHAT_ACTIVITY_MANAGER_TAG, "onResume -> ENTER");
        Log.i(CHAT_ACTIVITY_MANAGER_TAG, "onResume -> LEAVE");

    }

    @Override
    public void onDestroy() {
        Log.i(CHAT_ACTIVITY_MANAGER_TAG, "onDestroy -> ENTER");

        if (AppController.getInstanceParameterized(null).getNetworkConnectionStatus().equals(TorConstants.TOR_BUNDLE_STARTED)) {
            ChatController.getInstance().stoppedChatActivity(this, mSessionId);
        }

        Log.i(CHAT_ACTIVITY_MANAGER_TAG, "onDestroy -> LEAVE");
    }

    @Override
    public void sendMessage(String message) {
        Log.i(CHAT_ACTIVITY_MANAGER_TAG, "sendMessage -> ENTER message=" + message);

        if (AppController.getInstanceParameterized(null).getNetworkConnectionStatus().equals(TorConstants.TOR_BUNDLE_STARTED)) {
            ChatController.getInstance().sendMessage(mSessionId, message);
        }

        Log.i(CHAT_ACTIVITY_MANAGER_TAG, "sendMessage -> LEAVE");
    }

    @Override
    public void showReceivedMessage(final ChatMessage partnerMessage) {
        Log.i(CHAT_ACTIVITY_MANAGER_TAG, "showReceivedMessage -> ENTER");

        mChatActivity.runOnUiThread(new Runnable() {
            @Override
            public void run() {
                mChatActivity.showReceivedPartnerMessage(partnerMessage);
            }
        });

        Log.i(CHAT_ACTIVITY_MANAGER_TAG, "showReceivedMessage -> LEAVE");
    }

    @Override
    public void loadHistory() {
        Log.i(CHAT_ACTIVITY_MANAGER_TAG, "openHistory -> ENTER");
        Log.i(CHAT_ACTIVITY_MANAGER_TAG, "openHistory -> LEAVE");
    }

    @Override
    public void configureChatDetail(long sessionId) {
        Log.i(CHAT_ACTIVITY_MANAGER_TAG, "getChatDetail -> ENTER sessionId=" + sessionId);

        mSessionId = sessionId;

        Log.i(CHAT_ACTIVITY_MANAGER_TAG, "getChatDetail -> LEAVE");
    }

    @Override
    public void setChatActivity(ChatActivity chatActivity) {
        Log.i(CHAT_ACTIVITY_MANAGER_TAG, "setChatActivity -> ENTER chatActivity=" + chatActivity);

        mChatActivity = chatActivity;

        Log.i(CHAT_ACTIVITY_MANAGER_TAG, "setChatActivity -> LEAVE");
    }

    @Override
    public ChatDetail getChatDetail() {
        Log.i(CHAT_ACTIVITY_MANAGER_TAG, "getChatDetail -> ENTER");


        Log.i(CHAT_ACTIVITY_MANAGER_TAG, "getChatDetail -> LEAVE mChatDetail=" + mChatDetail);
        return mChatDetail;
    }

//    public void connectToPartner() {
//        Log.i(CHAT_ACTIVITY_MANAGER_TAG, "connectToPartner -> ENTER");
//        ChatController.getInstance().establishConnectionToPartner(this, mSessionId);
//        Log.i(CHAT_ACTIVITY_MANAGER_TAG, "connectToPartner -> LEAVE");
//    }
} public interface IChatActivityManager extends IViewModel {

    /**
     * Chat related methods
     */


//public void manageMessages();

    public void sendMessage(String message);

    public void loadHistory();

    public void setChatActivity(ChatActivity chatActivity);

    public void configureChatDetail(long sessionId);

    public ChatDetail getChatDetail();
} public class ChatMessage {
    private String mMessage;
    private ChatMessageType mChatMessageType;
    private long mTimeStamp;

    public ChatMessage(String message, ChatMessageType messageType, long timeStamp) {
        mMessage = message;
        mChatMessageType = messageType;
        mTimeStamp = timeStamp;
    }

    public String getMessage() {
        return mMessage;
    }

    public ChatMessageType getChatMessageType() {
        return mChatMessageType;
    }

    public long getTimeStamp() {
        return mTimeStamp;
    }
} public enum ChatMessageType {
    PARTNER, USER, SYNC, OTHER;
} public class EmptyHistoryPopup extends Dialog implements View.OnClickListener {

    private static final String LOG = EmptyHistoryPopup.class.getSimpleName();

    public EmptyHistoryPopup(@NonNull Context context) {
        super(context);
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        Log.i(LOG, "onCreate -> ENTER");
        super.onCreate(savedInstanceState);

        requestWindowFeature(Window.FEATURE_NO_TITLE);
        setContentView(R.layout.empty_history_popup);

        Log.i(LOG, "onCreate -> LEAVE");
    }

    @Override
    public void onClick(View view) {
        Log.i(LOG, "onClick -> ENTER");
        Log.i(LOG, "nothing");
        Log.i(LOG, "onClick -> LEAVE");
    }

    @Override
    public void onProvideKeyboardShortcuts(List<KeyboardShortcutGroup> data, @Nullable Menu menu, int deviceId) {

    }
} public class NoNetworkPopup extends Dialog implements View.OnClickListener {

    private static final String LOG = NoNetworkPopup.class.getSimpleName();

    private Button mConnectButton;

    public NoNetworkPopup(@NonNull Context context) {
        super(context);
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        Log.i(LOG, "onCreate -> ENTER");
        super.onCreate(savedInstanceState);

        requestWindowFeature(Window.FEATURE_NO_TITLE);
        setContentView(R.layout.no_network_popup);

        mConnectButton = (Button) findViewById(R.id.nnp_connect_button);
        mConnectButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Log.i(LOG, "mConnectButton.onClick -> ENTER");

                AppController.getInstanceParameterized(null).startNetworkConnection();

                Log.i(LOG, "mConnectButton.onClick -> LEAVE");
            }
        });

        Log.i(LOG, "onCreate -> LEAVE");
    }

    @Override
    public void onClick(View view) {
        Log.i(LOG, "onClick -> ENTER");
        Log.i(LOG, "nothing");
        Log.i(LOG, "onClick -> LEAVE");
    }

    @Override
    public void onProvideKeyboardShortcuts(List<KeyboardShortcutGroup> data, @Nullable Menu menu, int deviceId) {

    }
} public class PartnerOfflinePopup extends Dialog implements View.OnClickListener {

    private static final String LOG = PartnerOfflinePopup.class.getSimpleName();

    private IChatActivityManager mChatActivityManager;

    private Button mRetryButton;

    public PartnerOfflinePopup(@NonNull Context context, IChatActivityManager chatActivityManager) {
        super(context);
        mChatActivityManager = chatActivityManager;
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        Log.i(LOG, "onCreate -> ENTER");
        super.onCreate(savedInstanceState);

        requestWindowFeature(Window.FEATURE_NO_TITLE);
        setContentView(R.layout.partner_offline_popup);

        mRetryButton = (Button) findViewById(R.id.pop_retry_button);
        mRetryButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Log.i(LOG, "mRetryButton.onClick -> ENTER");

                mChatActivityManager.onCreate();

                Log.i(LOG, "mRetryButton.onClick -> LEAVE");
            }
        });

        Log.i(LOG, "onCreate -> LEAVE");
    }

    @Override
    public void onClick(View view) {
        Log.i(LOG, "onClick -> ENTER");
        Log.i(LOG, "nothing");
        Log.i(LOG, "onClick -> LEAVE");
    }

    @Override
    public void onProvideKeyboardShortcuts(List<KeyboardShortcutGroup> data, @Nullable Menu menu, int deviceId) {

    }
} public class DBChatMessageEntity implements IDbEntity {
    private long mId;
    private long mSessionId;
    private String mMessage;
    private long mTimestamp;
    private ChatMessageType mChatMessageType;

    public long getId() {
        return mId;
    }

    public void setId(long id) {
        this.mId = id;
    }

    public long getSessionId() {
        return mSessionId;
    }

    public void setSessionId(long sessionId) {
        this.mSessionId = sessionId;
    }

    public String getMessage() {
        return mMessage;
    }

    public void setMessage(String message) {
        this.mMessage = message;
    }

    public long getTimestamp() {
        return mTimestamp;
    }

    public void setTimestamp(long timestamp) {
        this.mTimestamp = timestamp;
    }

    public ChatMessageType getChatMessageType() {
        return mChatMessageType;
    }

    public void setChatMessageType(ChatMessageType chatMessageType) {
        this.mChatMessageType = chatMessageType;
    }

    @Override
    public String toString() {
        return "DBChatMessageEntity{" +
                "mId=" + mId +
                ", mSessionId=" + mSessionId +
                ", mMessage='" + mMessage + '\'' +
                ", mTimestamp=" + mTimestamp +
                ", mChatMessageType=" + mChatMessageType +
                '}';
    }
} public class DBContactEntity implements IDbEntity {
    private long mId;
    private long mSessionId;
    private String mAddress;
    private String mName;
    private String mNickName;
    private String mEmail;

    public long getId() {
        return mId;
    }

    public void setId(long id) {
        this.mId = id;
    }

    public long getSessionId() {
        return mSessionId;
    }

    public void setSessionId(long sessionId) {
        this.mSessionId = sessionId;
    }

    public String getEmail() {
        return mEmail;
    }

    public void setEmail(String email) {
        this.mEmail = email;
    }

    public String getAddress() {
        return mAddress;
    }

    public void setAddress(String address) {
        this.mAddress = address;
    }

    public String getName() {
        return mName;
    }

    public void setName(String name) {
        this.mName = name;
    }

    public String getNickName() {
        return mNickName;
    }

    public void setNickName(String nickName) {
        this.mNickName = nickName;
    }

    @Override
    public String toString() {
        return "DBContactEntity{" +
                "mId=" + mId +
                ", mSessionId=" + mSessionId +
                ", mAddress='" + mAddress + '\'' +
                ", mName='" + mName + '\'' +
                ", mNickName='" + mNickName + '\'' +
                ", mEmail='" + mEmail + '\'' +
                '}';
    }
} public interface IDbEntity {
} public interface TorConstants {
    // TOR Expert Bundle resources
    public static final String FOLDER_NAME_FOR_RESOURCES_IN_ASSETS = "tor_bundle";
    public static final String INTERNAL_RESOURCE_FOLDER_NAME = "/torBundle";
    public static final String TOR_READY_STATUS_MESSAGE = "Bootstrapped 100%: Done";
    public static final String TOR_HIDDEN_SERVICE_NAME = "hostname";

    public static final String TOR_ADDRESS_SUFFIX = ".onion";

    //Port related constants
    public static final int TOR_BUNDLE_EXTERNAL_PORT = 80;
    public static final int TOR_BUNDLE_INTERNAL_SOCKS_PORT = 11158;
    public static final int TOR_BUNDLE_INTERNAL_HIDDEN_SERVICES_PORT = 44444;

    //TOR Bundle Status constants
    public static final String TOR_BUNDLE_STOPPED = "disconnected";
    public static final String TOR_BUNDLE_IS_STARTING = "connecting";
    public static final String TOR_BUNDLE_STARTED = "connected";
}
import android.content.Context;
import android.content.Intent;
import android.graphics.Typeface;
import android.os.Bundle;
import android.support.v4.content.ContextCompat;
import android.support.v7.app.AppCompatActivity;
import android.text.Editable;
import android.text.TextWatcher;
import android.util.Log;
import android.view.KeyEvent;
import android.view.View;
import android.widget.EditText;
import android.widget.ImageView;
import android.widget.ListView;
import android.widget.RelativeLayout;
import android.widget.TextView;
import com.batsw.anonimitychat.R;
import com.batsw.anonimitychat.appManagement.AppController;
import com.batsw.anonimitychat.chat.constants.ChatModelConstants;
import com.batsw.anonimitychat.chat.management.activity.ChatActivityManagerImpl;
import com.batsw.anonimitychat.chat.management.activity.IChatActivityManager;
import com.batsw.anonimitychat.chat.message.ChatMessage;
import com.batsw.anonimitychat.chat.message.ChatMessageType;
import com.batsw.anonimitychat.mainScreen.popup.EmptyHistoryPopup;
import com.batsw.anonimitychat.mainScreen.popup.NoNetworkPopup;
import com.batsw.anonimitychat.mainScreen.popup.PartnerOfflinePopup;
import com.batsw.anonimitychat.persistence.entities.DBChatMessageEntity;
import com.batsw.anonimitychat.persistence.entities.DBContactEntity;
import com.batsw.anonimitychat.persistence.util.IDbEntity;
import com.batsw.anonimitychat.tor.bundle.TorConstants;
import java.util.ArrayList;
import java.util.List;
package com.batsw.anonimitychat.chat;




/**
 * Created by tudor on 1/27/2017.
 */

public class ChatActivity extends AppCompatActivity {

    // =================
    // === VARIABLES ===
    // =================

    private static final String LOG = ChatActivity.class.getSimpleName();

    private RelativeLayout reltiveLayout;

    private ListView chatListView;
    private ChatListAdapter mChatListAdapter;
    private ArrayList<ChatMessage> mChatMessageList;

    private long mSessionId = 0;

    private EditText chatEditText;

    private TextView mHistory, mChatName, mBack, mNetworkConnection;
    private TextView mConnectionStatus;

    private boolean mHistoryLoaded = false;

    private ImageView mEnterChatMessage;

    private IChatActivityManager mChatActivityManager = new ChatActivityManagerImpl();

    private NoNetworkPopup mNoNetworkPopup;
    private EmptyHistoryPopup mEmptyHistoryPopup;
    private PartnerOfflinePopup mPartnerOfflinePopup;

    /**
     * Sending the message when clicking on Send image
     */
    private ImageView.OnClickListener mClickForEnterChatView = new View.OnClickListener() {

        @Override
        public void onClick(View view) {
            Log.i(LOG, "mClickForEnterChatView.onClick -> ENTER");
            if (view.equals(mEnterChatMessage)) {

                if (AppController.getInstanceParameterized(null).getNetworkConnectionStatus().equals(TorConstants.TOR_BUNDLE_STARTED)) {

                    if (mChatActivityManager.getChatDetail().getTorConnection() != null) {

                        Log.i(LOG, "sending message to partner");

                        final ChatMessage chatMessage = new ChatMessage(chatEditText.getText().toString(), ChatMessageType.USER, System.currentTimeMillis());

                        //sending the message HERE
                        mChatActivityManager.sendMessage(chatMessage.getMessage());

                        mChatMessageList.add(chatMessage);

//                        binding MY message to this chat and have it to history
                        DBChatMessageEntity chatMessageEntity = new DBChatMessageEntity();
                        chatMessageEntity.setSessionId(mSessionId);
                        chatMessageEntity.setMessage(chatMessage.getMessage());
                        chatMessageEntity.setChatMessageType(chatMessage.getChatMessageType());
                        chatMessageEntity.setTimestamp(chatMessage.getTimeStamp());
                        AppController.getInstanceParameterized(null).addMessageToChatHistory(chatMessageEntity);

                        mNetworkConnection.setTextColor(ContextCompat.getColor(getApplicationContext(), R.color.userstatus_online));
                    } else {
                        mNetworkConnection.setTextColor(ContextCompat.getColor(getApplicationContext(), R.color.userstatus_busy_outline));

                        //                    means starting the connection to the partner
                        mChatActivityManager.onCreate();

                        mPartnerOfflinePopup.show();
                    }
                } else {
                    Log.i(LOG, "showing NoNetworkPopup");
                    mNoNetworkPopup.show();
                }
            }
            chatEditText.setText("");

            Log.i(LOG, "mClickForEnterChatView.onClick -> LEAVE");
        }
    };

    private final TextWatcher mChatEditTextWatcher = new TextWatcher() {
        @Override
        public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {

        }

        @Override
        public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {
            if (!chatEditText.getText().toString().isEmpty()) {
                mEnterChatMessage.setImageResource(R.drawable.input_send);
            }
        }

        @Override
        public void afterTextChanged(Editable editable) {
            mEnterChatMessage.setImageResource(R.drawable.input_send);
        }
    };

//    ========================
//    ===     METHODS      ===
//    ========================

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.chat_activity);

        reltiveLayout = (RelativeLayout) findViewById(R.id.relativeLayout);

        AppController.getInstanceParameterized(null).setChatControllerCurrentActivityContext(this);

        mChatActivityManager.setChatActivity(this);

        //Loading fontAwesome
        Typeface fontAwesome = Typeface.createFromAsset(getAssets(), "font_awesome/fontawesome.ttf");

        mSessionId = getIntent().getLongExtra(ChatModelConstants.CHAT_ACTIVITY_INTENT_EXTRA_KEY, ChatModelConstants.DEFAULT_SESSION_ID);
        if (mSessionId != ChatModelConstants.DEFAULT_SESSION_ID) {
            mChatActivityManager.configureChatDetail(mSessionId);
        }

        mChatMessageList = new ArrayList<>();

        mChatListAdapter = new ChatListAdapter(mChatMessageList, this);
        chatListView = (ListView) findViewById(R.id.chat_list_view);
        chatListView.setAdapter(mChatListAdapter);

        mEnterChatMessage = (ImageView) findViewById(R.id.enter_chat1);
        mEnterChatMessage.setOnClickListener(mClickForEnterChatView);

        // set image on click listener
        chatEditText = (EditText) findViewById(R.id.chat_edit_text1);
        /**
         * Sending the message when hitting enter on keyboard
         */
        chatEditText.setOnKeyListener(new View.OnKeyListener() {
            @Override
            public boolean onKey(View view, int keyCode, KeyEvent keyEvent) {
                return processChatEditTextKey(view, keyCode, keyEvent);
            }
        });

        chatEditText.addTextChangedListener(mChatEditTextWatcher);

        //TODO:
        //IChatActivityManager.getPartnerAddress (16x.onion)

        mBack = (TextView) findViewById(R.id.chat_back_icon);
        mBack.setTypeface(fontAwesome);
        mBack.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Log.i(LOG, "mBack.setOnClickListener -> ENTER");
                finish();
                Log.i(LOG, "mBack.setOnClickListener -> LEAVE");
            }
        });

        DBContactEntity contactEntity = AppController.getInstanceParameterized(null).getContactEntity(mSessionId);