package org.emdev.common.filesystem;

import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.net.Uri;

import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;

import org.emdev.common.log.LogContext;
import org.emdev.common.log.LogManager;
import org.emdev.utils.listeners.ListenerProxy;

public class MediaManager extends BroadcastReceiver {

    private static final LogContext LCTX = LogManager.root().lctx("MediaManager");

    public static final ListenerProxy listeners = new ListenerProxy(Listener.class);

    private static final Map<String, ExternalMedia> mountedMedia = new TreeMap<String, ExternalMedia>();

    private static final MediaManager instance = new MediaManager();

    private MediaManager() {
    }

    public static void init(final Context context) {
        readMounts();
        context.registerReceiver(instance, createIntentFilter());
    }

    static void readMounts() {
        try {
            final BufferedReader in = new BufferedReader(new FileReader("/proc/mounts"));
            try {
                for (String s = in.readLine(); s != null; s = in.readLine()) {
                    processMountPoint(s);
                }
            } catch (final IOException ex) {
                LCTX.e("Reading mounting points failed: " + ex.getMessage());
            } finally {
                try {
                    in.close();
                } catch (final IOException ex) {
                }
            }
        } catch (final FileNotFoundException ex) {
            LCTX.e("Reading mounting points failed: " + ex.getMessage());
        }
    }

    static void processMountPoint(final String s) {
        try {
            final MountedDevice d = new MountedDevice(s);
            if (d.device.startsWith("/dev/block/vold")) {
                final boolean rw = d.params.containsKey("rw");
                setMediaState(d.point, rw ? MediaState.MEDIA_MOUNTED : MediaState.MEDIA_MOUNTED_READ_ONLY);
            }
        } catch (final IllegalArgumentException th) {
            LCTX.w(th.getMessage());
        }
    }

    static IntentFilter createIntentFilter() {
        final IntentFilter f = new IntentFilter();
        f.addAction(Intent.ACTION_MEDIA_MOUNTED);
        f.addAction(Intent.ACTION_MEDIA_SHARED);
        f.addAction(Intent.ACTION_MEDIA_EJECT);
        f.addAction(Intent.ACTION_MEDIA_BAD_REMOVAL);
        f.addAction(Intent.ACTION_MEDIA_UNMOUNTABLE);
        f.addAction(Intent.ACTION_MEDIA_UNMOUNTED);
        f.addDataScheme("file");
        return f;
    }

    public static void onTerminate(final Context context) {
        context.unregisterReceiver(instance);
    }

    @Override
    public void onReceive(final Context context, final Intent intent) {
        final String action = intent.getAction();

        if (Intent.ACTION_MEDIA_MOUNTED.equals(action)) {
            final Uri data = intent.getData();
            final String path = PathFromUri.retrieve(context.getContentResolver(), data);
            final boolean readOnly = intent.getExtras().getBoolean("read-only");
            setMediaState(path, readOnly ? MediaState.MEDIA_MOUNTED : MediaState.MEDIA_MOUNTED_READ_ONLY);
            return;
        }

        if (Intent.ACTION_MEDIA_SHARED.equals(action)) {
            final Uri data = intent.getData();
            final String path = PathFromUri.retrieve(context.getContentResolver(), data);
            setMediaState(path, MediaState.MEDIA_SHARED);
            return;
        }

        if (Intent.ACTION_MEDIA_EJECT.equals(action)) {
            final Uri data = intent.getData();
            final String path = PathFromUri.retrieve(context.getContentResolver(), data);
            setMediaState(path, MediaState.MEDIA_REMOVED);
            return;
        }

        if (Intent.ACTION_MEDIA_BAD_REMOVAL.equals(action)) {
            final Uri data = intent.getData();
            final String path = PathFromUri.retrieve(context.getContentResolver(), data);
            setMediaState(path, MediaState.MEDIA_BAD_REMOVAL);
            return;
        }

        if (Intent.ACTION_MEDIA_UNMOUNTABLE.equals(action)) {
            final Uri data = intent.getData();
            final String path = PathFromUri.retrieve(context.getContentResolver(), data);
            setMediaState(path, MediaState.MEDIA_UNMOUNTABLE);
            return;
        }

        if (Intent.ACTION_MEDIA_UNMOUNTED.equals(action)) {
            final Uri data = intent.getData();
            final String path = PathFromUri.retrieve(context.getContentResolver(), data);
            setMediaState(path, MediaState.MEDIA_UNMOUNTED);
            return;
        }

    }

    public static synchronized Collection<String> getReadableMedia() {
        final List<String> list = new ArrayList<String>(mountedMedia.size());
        for (final ExternalMedia m : MediaManager.mountedMedia.values()) {
            if (m.state.readable) {
                list.add(m.path);
            }
        }
        return list;
    }

    static synchronized void setMediaState(final String path, final MediaState newState) {
        MediaState oldState = null;
        ExternalMedia media = mountedMedia.get(path);
        if (media == null) {
            media = new ExternalMedia(path, newState);
            mountedMedia.put(path, media);
        } else {
            oldState = media.state;
            media.state = newState;
        }
        final Listener l = listeners.getListener();

        LCTX.d(path + " : " + oldState + " -> " + newState);

        l.onMediaStateChanged(path, oldState, media.state);
    }

    public static interface Listener {

        void onMediaStateChanged(String path, MediaState oldState, MediaState newState);
    }

    private static class MountedDevice {

        public final String device;
        public final String point;
        public final Map<String, String> params = new LinkedHashMap<String, String>();

        public MountedDevice(final String s) {
            final String[] fields = s.split("\\s");
            if (fields.length < 4) {
                throw new IllegalArgumentException("Bad moint point string: " + s);
            }
            device = fields[0];
            point = fields[1];

            final String[] pp = fields[3].split(",");
            for (int i = 0; i < pp.length; i++) {
                String key = pp[i];
                String val = key;

                final int index = key.indexOf("=");
                if (index != -1) {
                    key = key.substring(0, index);
                    val = val.substring(index + 1);
                }
                params.put(key, val);
            }
        }
    }
}
