package com.azcltd.fluffyevents;

import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.IntentFilter.MalformedMimeTypeException;
import android.os.Bundle;
import android.util.SparseArray;

import java.util.HashMap;
import java.util.Map;
import java.util.Random;

public final class EventsBus {

    private static final String RND = String.valueOf(new Random().nextInt(Integer.MAX_VALUE));

    private static final String ACTION_NOTIFIER_PREFIX = "action_fluffy_events_" + RND + "_";
    private static final String BASE_MIME_TYPE = "vnd.fluffy.events." + RND;
    private static final String EVENT_MIME_TYPE_ = BASE_MIME_TYPE + "/event_";

    private static final String EXTRA_EVENT_ID = "extra_event_id_" + RND;
    private static final String EXTRA_RECEIVER_ID = "extra_receiver_id_" + RND;

    private static int sRegistrationId;

    private static Context sAppContext;
    private static String sIntentAction;
    private static final SparseArray<EventsReceiver> sReceiversMap = new SparseArray<EventsReceiver>();
    private static final Map<String, Intent> sStickyEventsMap = new HashMap<String, Intent>();

    public static void init(Context context) {
        if (sAppContext == null) {
            sAppContext = context.getApplicationContext();
            sIntentAction = ACTION_NOTIFIER_PREFIX + sAppContext.getPackageName();
        }
    }

    private static void check() {
        if (sAppContext == null) throw new RuntimeException("EventsBus was not initialized with init(Context) method");
    }

    /**
     * @return Registration id to use in unregister(regId) method
     */
    public static synchronized int register(EventsListener listener) {
        return register(null, listener);
    }

    /**
     * @return Registration id to use in unregister(regId) method
     */
    public static synchronized int register(String receiverId, EventsListener listener) {
        check();

        if (listener == null) throw new NullPointerException("Listener cannot be null");

        EventsReceiver receiver = new EventsReceiver();
        receiver.mReceiverId = receiverId;
        receiver.mListener = listener;

        try {
            sAppContext.registerReceiver(receiver, new IntentFilter(sIntentAction, BASE_MIME_TYPE + "/*"));
        } catch (MalformedMimeTypeException e) {
            e.printStackTrace();
        }

        sReceiversMap.put(++sRegistrationId, receiver);

        for (Intent sticky : sStickyEventsMap.values()) {
            receiver.onReceive(sAppContext, sticky);
        }

        return sRegistrationId;
    }

    public static synchronized void unregister(int regId) {
        check();
        EventsReceiver receiver = sReceiversMap.get(regId);
        sReceiversMap.delete(regId);
        if (receiver != null) sAppContext.unregisterReceiver(receiver);
    }

    public static void send(int eventId) {
        send(eventId, null, false, null);
    }

    public static void send(int eventId, Bundle params) {
        send(eventId, params, false, null);
    }

    public static void send(int eventId, String receiverId) {
        send(eventId, null, false, receiverId);
    }

    public static void send(int eventId, String receiverId, Bundle params) {
        send(eventId, params, false, receiverId);
    }

    public static void sendSticky(int eventId) {
        send(eventId, null, true, null);
    }

    public static void sendSticky(int eventId, Bundle params) {
        send(eventId, params, true, null);
    }

    public static void sendSticky(int eventId, String receiverId) {
        send(eventId, null, true, receiverId);
    }

    public static void sendSticky(int eventId, String receiverId, Bundle params) {
        send(eventId, params, true, receiverId);
    }

    private synchronized static void send(int eventId, Bundle params, boolean sticky, String receiverId) {
        check();
        String mimeType = buildMimeType(eventId, receiverId);
        Intent intent = new Intent(sIntentAction).setType(mimeType);

        if (params != null) intent.putExtras(params);
        intent.putExtra(EXTRA_EVENT_ID, eventId);
        if (receiverId != null) intent.putExtra(EXTRA_RECEIVER_ID, receiverId);

        if (sticky) sStickyEventsMap.put(mimeType, intent);

        sAppContext.sendBroadcast(intent);
    }

    /**
     * Removes sticky event for given id.<br/>
     * Notification with event id = "-eventId" will be send, if sticky event was successfully removed.
     */
    public static void removeSticky(int eventId) {
        removeSticky(eventId, null);
    }

    /**
     * Removes sticky event for given id and given receiver.<br/>
     * Notification with event id = "-eventId" will be send, if sticky event was successfully removed.
     */
    public synchronized static void removeSticky(int eventId, String receiverId) {
        check();

        String mimeType = buildMimeType(eventId, receiverId);
        Intent sticky = sStickyEventsMap.remove(mimeType);
        if (sticky != null) send(-eventId, receiverId, sticky.getExtras());
    }

    private static String buildMimeType(int eventId, String receiverId) {
        return EVENT_MIME_TYPE_ + eventId + (receiverId == null ? "" : "_" + receiverId);
    }

    private static class EventsReceiver extends BroadcastReceiver {

        private String mReceiverId;
        private EventsListener mListener;

        @Override
        public final void onReceive(Context context, Intent intent) {
            if (intent.hasExtra(EXTRA_EVENT_ID)) {
                Bundle params = intent.getExtras();
                String targetReceiverId = params.getString(EXTRA_RECEIVER_ID);

                if (targetReceiverId == null || targetReceiverId.equals(mReceiverId)) {
                    mListener.onEvent(params.getInt(EXTRA_EVENT_ID), params, targetReceiverId == null);
                }
            }
        }

    }

}