/*
 * Copyright (c) 2015 LingoChamp Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.liulishuo.filedownloader.event;

import android.os.Handler;
import android.os.Looper;

import com.liulishuo.filedownloader.util.FileDownloadLog;

import junit.framework.Assert;

import java.util.HashMap;
import java.util.LinkedList;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

/**
 * Created by Jacksgong on 15/6/23.
 */
public class DownloadEventPoolImpl implements IDownloadEventPool {

    private final ExecutorService threadPool = new ThreadPoolExecutor(3, 30,
            10, TimeUnit.SECONDS,
            new LinkedBlockingQueue<Runnable>());

    private final HashMap<String, LinkedList<IDownloadListener>> listenersMap = new HashMap<>();

    private final Handler handler;

    public DownloadEventPoolImpl() {
        handler = new Handler(Looper.getMainLooper());
    }

    @Override
    public boolean addListener(final String eventId, final IDownloadListener listener) {
        if (FileDownloadLog.NEED_LOG) {
            FileDownloadLog.v(this, "setListener %s", eventId);
        }
        Assert.assertNotNull("EventPoolImpl.add", listener);

        LinkedList<IDownloadListener> container = listenersMap.get(eventId);

        if (container == null) {
            synchronized (eventId) {
                container = listenersMap.get(eventId);
                if (container == null) {
                    listenersMap.put(eventId, container = new LinkedList<>());
                }
            }
        }


        synchronized (eventId) {
            return container.add(listener);
        }
    }

    @Override
    public boolean removeListener(final String eventId, final IDownloadListener listener) {
        if (FileDownloadLog.NEED_LOG) {
            FileDownloadLog.v(this, "removeListener %s", eventId);
        }
//        Assert.assertNotNull("EventPoolImpl.remove", listener);

        LinkedList<IDownloadListener> container = listenersMap.get(eventId);
        if (container == null) {
            synchronized (eventId) {
                container = listenersMap.get(eventId);
            }
        }

        if (container == null || listener == null) {
            return false;
        }

        synchronized (eventId) {
            boolean succeed = container.remove(listener);
            if (container.size() <= 0) {
                listenersMap.remove(container);
            }
            return succeed;
        }
    }

    public boolean post2UI(final Runnable runnable) {
        if (handler == null) {
            return false;
        }
        return handler.post(runnable);
    }

    @Override
    public boolean publish(final IDownloadEvent event) {
        if (FileDownloadLog.NEED_LOG) {
            FileDownloadLog.v(this, "publish %s", event.getId());
        }
        Assert.assertNotNull("EventPoolImpl.publish", event);
        String eventId = event.getId();
        LinkedList<IDownloadListener> listeners = listenersMap.get(eventId);
        if (listeners == null) {
            synchronized (eventId) {
                listeners = listenersMap.get(eventId);
                if (listeners == null) {
                    if (FileDownloadLog.NEED_LOG) {
                        FileDownloadLog.d(this, "No listener for this event %s", eventId);
                    }
                    return false;
                }
            }
        }

        trigger(listeners, event);
        return true;
    }

    @Override
    public void asyncPublish(final IDownloadEvent event, final Looper looper) {
        if (FileDownloadLog.NEED_LOG) {
            FileDownloadLog.v(this, "asyncPublish %s", event.getId());
        }
        Assert.assertNotNull("EventPoolImpl.asyncPublish event", event);
        Assert.assertNotNull("EventPoolImpl.asyncPublish looper", looper);
        Handler handler = new Handler(looper);
        handler.post(new Runnable() {
            @Override
            public void run() {
                DownloadEventPoolImpl.this.publish(event);
            }
        });
    }


    @Override
    public void asyncPublishInNewThread(final IDownloadEvent event) {
        if (FileDownloadLog.NEED_LOG) {
            FileDownloadLog.v(this, "asyncPublishInNewThread %s", event.getId());
        }
        Assert.assertNotNull("EventPoolImpl.asyncPublish event", event);

        threadPool.execute(new Runnable() {
            @Override
            public void run() {
                DownloadEventPoolImpl.this.publish(event);
            }
        });
    }

    @Override
    public void asyncPublishInMain(final IDownloadEvent event) {
        post2UI(new Runnable() {
            @Override
            public void run() {
                DownloadEventPoolImpl.this.publish(event);
            }
        });
    }

    private void trigger(final LinkedList<IDownloadListener> listeners, final IDownloadEvent event) {
        // do not handle Order.
//        try {
//            if (event.getOrder()) {
//                Collections.sort(listeners, new Comparator<IDownloadListener>() {
//                    @Override
//                    public int compare(IDownloadListener lhs, IDownloadListener rhs) {
//                        return rhs.getPriority() - lhs.getPriority();
//                    }
//                });
//            }
//
//        } catch (Exception e) {
//            FileDownloadLog.e(this, e, "trigger error, %s", event != null ? event.getId() : null);
//        }

        final Object[] lists = listeners.toArray();
        for (Object o : lists) {
            if (((IDownloadListener) o).callback(event)) {
                break;
            }
        }

        if (event.callback != null) {
            event.callback.run();
        }
    }

    @Override
    public boolean hasListener(final IDownloadEvent event) {
        if (FileDownloadLog.NEED_LOG) {
            FileDownloadLog.v(this, "hasListener %s", event.getId());
        }
        Assert.assertNotNull("EventPoolImpl.hasListener", event);
        String eventId = event.getId();
        LinkedList<IDownloadListener> listeners = listenersMap.get(eventId);
        return listeners != null && listeners.size() > 0;
    }
}

@@ -1944,36 +1944,45 @@
 ronized (eventId
+.intern()
 ) %7B%0A
-
                 
@@ -2205,32 +2205,41 @@
 ronized (eventId
+.intern()
 ) %7B%0A            
@@ -2712,32 +2712,41 @@
 ronized (eventId
+.intern()
 ) %7B%0A            
@@ -2924,32 +2924,41 @@
 ronized (eventId
+.intern()
 ) %7B%0A            
@@ -3709,32 +3709,32 @@
 ners == null) %7B%0A
-
             sync
@@ -3750,16 +3750,25 @@
 (eventId
+.intern()
 ) %7B%0A    

use the internal-string as synchronized lock-object instead of string-original