package com.romainpiel.meatspace.service;

import android.app.Notification;
import android.app.PendingIntent;
import android.app.Service;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Handler;
import android.os.IBinder;
import android.support.v4.app.NotificationCompat;
import android.widget.RemoteViews;

import com.google.gson.Gson;
import com.koushikdutta.async.http.socketio.Acknowledge;
import com.koushikdutta.async.http.socketio.ConnectCallback;
import com.koushikdutta.async.http.socketio.DisconnectCallback;
import com.koushikdutta.async.http.socketio.ErrorCallback;
import com.koushikdutta.async.http.socketio.EventCallback;
import com.koushikdutta.async.http.socketio.SocketIOClient;
import com.romainpiel.Constants;
import com.romainpiel.lib.api.ApiManager;
import com.romainpiel.lib.api.IOState;
import com.romainpiel.lib.bus.BusManager;
import com.romainpiel.lib.bus.ChatEvent;
import com.romainpiel.lib.bus.MuteEvent;
import com.romainpiel.lib.bus.UIEvent;
import com.romainpiel.lib.helper.PreferencesHelper;
import com.romainpiel.lib.utils.BackgroundExecutor;
import com.romainpiel.lib.utils.CacheManager;
import com.romainpiel.lib.utils.Debug;
import com.romainpiel.meatspace.R;
import com.romainpiel.meatspace.activity.MainActivity;
import com.romainpiel.model.Chat;
import com.romainpiel.model.ChatList;
import com.romainpiel.model.ChatRequest;
import com.romainpiel.model.Device;
import com.romainpiel.model.SocketChatEvent;
import com.squareup.otto.Produce;
import com.squareup.otto.Subscribe;

import java.util.Collection;
import java.util.HashSet;
import java.util.List;

/**
 * Meatspace
 * User: romainpiel
 * Date: 03/11/2013
 * Time: 17:47
 */
public class ChatService extends Service implements ConnectCallback, EventCallback, ErrorCallback, DisconnectCallback {

    private static final String API_GET_CHAT_REQ_ID = "ChatService.GET_CHAT";
    private static final String API_CACHE_MUTED_REQ_ID = "ChatService.CACHE_MUTED";
    private static final String API_GET_CACHED_MUTED_REQ_ID = "ChatService.GET_CACHED_MUTED";

    private ApiManager apiManager;
    private BusManager busManager;
    private BroadcastReceiver closeChatReceiver;
    private SocketIOClient socketIOClient;
    private Handler handler;
    private ChatList chatList;
    private HashSet<String> mutedUsers;
    private IOState ioState;
    private boolean appInBackground;
    private int missedMessageCount;
    private Runnable autoKillTimeoutBgRunnable;
    private CacheManager cacheManager;

    public static void start(Context context) {
        context.startService(new Intent(context, ChatService.class));
    }

    public static void stop(Context context) {
        context.stopService(new Intent(context, ChatService.class));
    }

    @Override
    public void onCreate() {
        super.onCreate();

        cacheManager = new CacheManager(this, false);
        apiManager = ApiManager.get();
        busManager = BusManager.get();
        handler = new Handler();

        ioState = IOState.IDLE;
        chatList = new ChatList();
        mutedUsers = new HashSet<String>();

        BackgroundExecutor.execute(new Runnable() {
            @Override
            public void run() {
                final HashSet<String> cachedMutedUsers = (HashSet<String>) cacheManager.readFile(Constants.CACHE_MUTED_USERS, null);

                if (cachedMutedUsers != null) {
                    handler.post(new Runnable() {
                        @Override
                        public void run() {
                            mutedUsers = cachedMutedUsers;
                            if (chatList.get() != null && !chatList.get().isEmpty()) {
                                syncChatList(chatList);
                                post();
                            }
                        }
                    });
                }
            }
        }, API_GET_CACHED_MUTED_REQ_ID, null);

        busManager.getChatBus().register(this);
        busManager.getUiBus().register(this);

        closeChatReceiver = new BroadcastReceiver() {
            @Override
            public void onReceive(Context context, Intent intent) {
                ioState = IOState.DISCONNECTED;
                post();
                context.stopService(new Intent(context, ChatService.class));
            }
        };
        registerReceiver(closeChatReceiver, new IntentFilter(Constants.FILTER_CHAT_CLOSE));
    }

    @Override
    public void onDestroy() {
        if (socketIOClient != null) {
            apiManager.disconnect(socketIOClient);
        }
        busManager.getChatBus().unregister(this);
        busManager.getUiBus().unregister(this);
        unregisterReceiver(closeChatReceiver);
        BackgroundExecutor.cancelAll(API_GET_CHAT_REQ_ID, true);
        BackgroundExecutor.cancelAll(API_GET_CACHED_MUTED_REQ_ID, true);
        BackgroundExecutor.cancelAll(API_CACHE_MUTED_REQ_ID, true);
        super.onDestroy();
    }

    @Override
    public int onStartCommand(final Intent intent, final int flags, final int startId) {

        if (!ioState.equals(IOState.CONNECTING) && !ioState.equals(IOState.CONNECTED)) {
            ioState = IOState.CONNECTING;

            // TODO avoid this and handle duplicates inside this class
            chatList.clear();

            apiManager.connect(this, this);
        }

        return START_STICKY;
    }

    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }

    /**
     * Socket connection callback
     *
     * @param ex     potential exception if error
     * @param client socket client
     */
    @Override
    public void onConnectCompleted(final Exception ex, final SocketIOClient client) {

        handler.post(new Runnable() {
            @Override
            public void run() {
                if (ex != null) {
                    postError();
                    return;
                }

                ioState = IOState.CONNECTED;

                socketIOClient = client;
                socketIOClient.setErrorCallback(ChatService.this);
                socketIOClient.setDisconnectCallback(ChatService.this);
                socketIOClient.addListener(ChatService.this);

                showForeground();

                post();
            }
        });
    }

    /**
     * place service in foreground or update its notification
     */
    private void showForeground() {

        Intent openIntent = new Intent(this, MainActivity.class);
        openIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP);

        PendingIntent pi =
                PendingIntent.getActivity(this, 0, openIntent, PendingIntent.FLAG_UPDATE_CURRENT);

        NotificationCompat.Builder builder = new NotificationCompat.Builder(this);

        String description;
        if (appInBackground && missedMessageCount > 0) {
            description = getResources().getQuantityString(R.plurals.service_chat_running_description_missed_messages_, missedMessageCount, missedMessageCount);
            if (PreferencesHelper.areNotificationsEnabled(this)) {
                String lastMessage = chatList.get().last().getValue().getMessage();
                builder.setTicker(lastMessage);
            }
        } else {
            description = getString(R.string.service_chat_running_description);
        }

        RemoteViews notificationView = new RemoteViews(this.getPackageName(), R.layout.notification_template);
        notificationView.setTextViewText(R.id.notification_template_title, getString(R.string.service_chat_running));
        notificationView.setTextViewText(R.id.notification_template_text2, description);
        notificationView.setOnClickPendingIntent(R.id.notification_template_cancel,
                PendingIntent.getBroadcast(this, 0, new Intent(Constants.FILTER_CHAT_CLOSE), 0));

        Notification notification = builder.setContentIntent(pi)
                .setSmallIcon(R.drawable.ic_stat_meatspace)
                .setContent(notificationView)
                .build();

        notification.flags |= Notification.FLAG_NO_CLEAR;

        startForeground(Constants.NOTIFICICATION_ID_CHAT, notification);
    }

    /**
     * Socket event callback
     *
     * @param dataString  raw data of the message
     * @param acknowledge socket channel details
     */
    @Override
    public void onEvent(final String dataString, Acknowledge acknowledge) {

        final Gson jsonParser = apiManager.getJsonParser();

        BackgroundExecutor.execute(new Runnable() {
            @Override
            public void run() {
                try {

                    final SocketChatEvent event = jsonParser.fromJson(dataString, SocketChatEvent.class);
                    final String name = event.getName();
                    final List<Chat> chats = event.getChats();

                    if (!ApiManager.EVENT_MESSAGE.equals(name) || chats == null)
                        return;

                    handler.post(new Runnable() {
                        @Override
                        public void run() {
                            ChatList newChats = new ChatList(chats);
                            syncChatList(newChats);
                            saveAndPost(newChats);

                            int newMissedMessageCount = 0;
                            for (Chat chat : chats) {
                                // don't count if it's from me
                                if (!chat.getValue().isFromMe()) {
                                    newMissedMessageCount++;
                                }
                            }
                            if (appInBackground && newMissedMessageCount > 0) {
                                missedMessageCount += newMissedMessageCount;
                                showForeground();
                            }
                        }
                    });

                } catch (Exception e) {
                    Debug.out(e);
                }
            }
        });
    }

    /**
     * a new chat request to post was posted
     *
     * @param chatRequest chat request to post
     */
    @Subscribe
    public void onEvent(ChatRequest chatRequest) {
        if (socketIOClient != null && socketIOClient.isConnected()) {
            Gson jsonParser = apiManager.getJsonParser();

            // 4 : json type (? not sure why)
            socketIOClient.emitRaw(4, jsonParser.toJson(chatRequest), null);
        } else {
            postError();
        }
    }

    /**
     * a user mute was requested
     *
     * @param muteEvent associated mute event
     */
    @Subscribe
    public void onEvent(MuteEvent muteEvent) {
        if (muteEvent.isMuted()) {
            if (muteEvent.getFingerprint() != null) {
                mutedUsers.add(muteEvent.getFingerprint());
            }
        } else if (muteEvent.getFingerprint() != null) {
            mutedUsers.remove(muteEvent.getFingerprint());
        } else if (muteEvent.getFingerprint() == null) {
            mutedUsers.clear();
        }

        BackgroundExecutor.execute(new Runnable() {
            @Override
            public void run() {
                cacheManager.writeFile(Constants.CACHE_MUTED_USERS, mutedUsers);
            }
        }, API_CACHE_MUTED_REQ_ID, null);

        syncChatList(chatList);
        post();
    }

    @Subscribe
    public void onEvent(UIEvent uiEvent) {
        missedMessageCount = 0;
        appInBackground = uiEvent == UIEvent.BACKGROUND;
        showForeground();

        if (uiEvent == UIEvent.BACKGROUND) {
            int autokillTimeout = PreferencesHelper.getAutoKillTimeoutBg(this);
            if (autokillTimeout >= 0) {
                autoKillTimeoutBgRunnable = new Runnable() {
                    @Override
                    public void run() {
                        if (BusManager.get().getUiBus().getLastEvent() == UIEvent.BACKGROUND) {
                            sendBroadcast(new Intent(Constants.FILTER_CHAT_CLOSE));
                        }
                    }
                };
                handler.postDelayed(autoKillTimeoutBgRunnable, autokillTimeout * 60 * 1000);
            }
        } else if (autoKillTimeoutBgRunnable != null) {
            handler.removeCallbacks(autoKillTimeoutBgRunnable);
            autoKillTimeoutBgRunnable = null;
        }
    }

    /**
     * synchronize the chat list:
     * - with the muted users list
     * - init isFromMe variable
     *
     * @param chatList chat list to sync
     */
    private void syncChatList(ChatList chatList) {

        String myFingerprint = new Device(this).getId();

        Collection<Chat> list = chatList.get();
        Chat.Value chatValue;
        String fingerprint;
        for (Chat chat : list) {
            chatValue = chat.getValue();
            fingerprint = chatValue.getFingerprint();
            chatValue.setMuted(mutedUsers.contains(fingerprint));
            chatValue.setFromMe(fingerprint.equals(myFingerprint));
        }
    }

    /**
     * set current ioState to ERROR then post an event on the event bus
     */
    public void postError() {
        ioState = IOState.ERROR;
        post();
    }

    /**
     * add items to current chat list and post an event to the event bus
     *
     * @param items items to add
     */
    public void saveAndPost(ChatList items) {
        this.chatList.addAll(items.get());
        post();
    }

    /**
     * post an event to the event bus
     */
    public void post() {
        this.busManager.getChatBus().post(new ChatEvent(false, ioState, chatList));
    }

    @Produce
    public ChatEvent produce() {
        return new ChatEvent(true, ioState, chatList);
    }

    @Override
    public void onError(String error) {
        postError();
    }

    @Override
    public void onDisconnect(Exception e) {
        sendBroadcast(new Intent(Constants.FILTER_CHAT_CLOSE));
    }
}