/*
 * Project:  NextGIS Mobile
 * Purpose:  Mobile GIS for Android.
 * Author:   Dmitry Baryshnikov (aka Bishop), [email protected]
 * Author:   NikitaFeodonit, [email protected]
 * Author:   Stanislav Petriakov, [email protected]
 * *****************************************************************************
 * Copyright (c) 2012-2015. NextGIS, [email protected]
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser Public License for more details.
 *
 * You should have received a copy of the GNU Lesser Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package com.nextgis.maplib.map;

import android.content.Context;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.util.Log;

import com.nextgis.maplib.api.ILayer;
import com.nextgis.maplib.api.MapEventListener;
import com.nextgis.maplib.datasource.GeoPoint;
import com.nextgis.maplib.util.Constants;

import java.io.File;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import static com.nextgis.maplib.util.Constants.TAG;


public class MapEventSource
        extends MapContentProviderHelper
{
    protected static final String BUNDLE_ID_KEY             = "id";
    protected static final String BUNDLE_TYPE_KEY           = "type";
    protected static final String BUNDLE_DONE_KEY           = "done";
    protected static final String BUNDLE_ZOOM_KEY           = "zoom";
    protected static final String BUNDLE_X_KEY              = "x";
    protected static final String BUNDLE_Y_KEY              = "y";
    protected final static int    EVENT_onLayerAdded        = 1;
    protected final static int    EVENT_onLayerDeleted      = 2;
    protected final static int    EVENT_onLayerChanged      = 3;
    protected final static int    EVENT_onExtentChanged     = 4;
    protected final static int    EVENT_onLayersReordered   = 5;
    protected final static int    EVENT_onLayerDrawFinished = 6;
    protected final static int    EVENT_onLayerDrawStarted  = 7;
    protected        List<MapEventListener> mListeners;
    protected static Handler                mHandler;
    protected        boolean                mFreeze;

    protected Map<Integer, Long> mLastMessages;
    //skip event timeout ms
    public static final int SKIP_TIMEOUT = 450;

    public MapEventSource(
            Context context,
            File mapPath,
            LayerFactory layerFactory)
    {
        super(context, mapPath, layerFactory);
        mListeners = new ArrayList<>();
        mFreeze = false;
        mLastMessages = new HashMap<>();

        createHandler();
    }


    /**
     * Add new listener for map events
     *
     * @param listener
     *         A listener class implements MapEventListener adding to listeners array
     */
    public void addListener(MapEventListener listener)
    {
        if (mListeners != null && !mListeners.contains(listener)) {
            mListeners.add(listener);
        }
    }


    /**
     * Remove listener from listeners
     *
     * @param listener
     *         A listener class implements MapEventListener removing from listeners array
     */
    public void removeListener(MapEventListener listener)
    {
        if (mListeners != null) {
            mListeners.remove(listener);
        }
    }


    @Override
    public void onDrawFinished(
            int id,
            float percent)
    {
        super.onDrawFinished(id, percent);
        onLayerDrawFinished(id, percent);
    }


    /**
     * Send layer added event to all listeners
     *
     * @param layer
     *         A new layer
     */
    @Override
    protected void onLayerAdded(ILayer layer)
    {
        super.onLayerAdded(layer);
        if (mListeners == null) {
            return;
        }

        Bundle bundle = new Bundle();
        bundle.putInt(BUNDLE_ID_KEY, layer.getId());
        bundle.putInt(BUNDLE_TYPE_KEY, EVENT_onLayerAdded);

        Message msg = new Message();
        msg.setData(bundle);
        mHandler.sendMessage(msg);
    }


    /**
     * Send layer changed event to all listeners
     *
     * @param layer
     *         A changed layer
     */
    @Override
    protected void onLayerChanged(ILayer layer)
    {
        super.onLayerChanged(layer);
        if (mListeners == null) {
            return;
        }

        Bundle bundle = new Bundle();
        bundle.putInt(BUNDLE_ID_KEY, layer.getId());
        bundle.putInt(BUNDLE_TYPE_KEY, EVENT_onLayerChanged);

        Message msg = new Message();
        msg.setData(bundle);
        mHandler.sendMessage(msg);
    }


    /**
     * Send layer delete event to all listeners
     *
     * @param id
     *         A deleted layer identificator
     */
    @Override
    protected void onLayerDeleted(int id)
    {
        super.onLayerDeleted(id);
        if (mListeners == null) {
            return;
        }

        Bundle bundle = new Bundle();
        bundle.putInt(BUNDLE_ID_KEY, id);
        bundle.putInt(BUNDLE_TYPE_KEY, EVENT_onLayerDeleted);

        Message msg = new Message();
        msg.setData(bundle);
        mHandler.sendMessage(msg);
    }


    /**
     * Send extent change event to all listeners
     *
     * @param zoom
     *         A zoom level
     * @param center
     *         A map center coordinates
     */
    @Override
    protected void onExtentChanged(
            float zoom,
            GeoPoint center)
    {
        super.onExtentChanged(zoom, center);
        if (mListeners == null) {
            return;
        }

        Bundle bundle = new Bundle();
        bundle.putFloat(BUNDLE_ZOOM_KEY, zoom);
        bundle.putDouble(BUNDLE_X_KEY, center.getX());
        bundle.putDouble(BUNDLE_Y_KEY, center.getY());
        bundle.putInt(BUNDLE_TYPE_KEY, EVENT_onExtentChanged);

        Message msg = new Message();
        msg.setData(bundle);
        mHandler.sendMessage(msg);
    }


    /**
     * Send layers reordered event to all listeners
     */
    @Override
    protected void onLayersReordered()
    {
        super.onLayersReordered();
        if (mListeners == null) {
            return;
        }

        Bundle bundle = new Bundle();
        bundle.putInt(BUNDLE_TYPE_KEY, EVENT_onLayersReordered);

        Message msg = new Message();
        msg.setData(bundle);
        mHandler.sendMessage(msg);
    }


    /**
     * Send layers draw finished event to all listeners
     */
    protected void onLayerDrawFinished(
            int id,
            float percent)
    {
        if (mListeners == null) {
            return;
        }

        Bundle bundle = new Bundle();
        bundle.putInt(BUNDLE_TYPE_KEY, EVENT_onLayerDrawFinished);
        bundle.putInt(BUNDLE_ID_KEY, id);
        bundle.putFloat(BUNDLE_DONE_KEY, percent);

        Message msg = new Message();
        msg.setData(bundle);
        mHandler.sendMessage(msg);
    }


    /**
     * Send layers draw started event to all listeners
     */
    protected void onLayerDrawStarted()
    {
        if (mListeners == null) {
            return;
        }

        Bundle bundle = new Bundle();
        bundle.putInt(BUNDLE_TYPE_KEY, EVENT_onLayerDrawStarted);

        Message msg = new Message();
        msg.setData(bundle);
        mHandler.sendMessage(msg);
    }


    /**
     * Create handler for messages
     */
    protected void createHandler()
    {
        mHandler = new Handler()
        {
            public void handleMessage(Message msg)
            {
                super.handleMessage(msg);

                if (mFreeze) {
                    return;
                }

                Bundle resultData = msg.getData();

                Long lastTime = mLastMessages.get(resultData.getInt(BUNDLE_TYPE_KEY));
                if(lastTime != null && System.currentTimeMillis() - lastTime < SKIP_TIMEOUT){
                    boolean filter = !(EVENT_onExtentChanged == resultData.getInt(BUNDLE_TYPE_KEY) ||
                            EVENT_onLayerDrawFinished == resultData.getInt(BUNDLE_TYPE_KEY));
                    if(filter) {
                        if(Constants.DEBUG_MODE) {
                            Log.d(TAG, "handleMessage: skip event: " + resultData.getInt(BUNDLE_TYPE_KEY));
                        }
                        return;
                    }
//                    else{
//                        if(resultData.getFloat(BUNDLE_DONE_KEY) < 1){
//
//                            if(Constants.DEBUG_MODE) {
//                                Log.d(TAG, "handleMessage: skip event: " + resultData.getInt(BUNDLE_TYPE_KEY));
//                            }
//
//                            return;
//                        }
//                    }
                }
                mLastMessages.put(resultData.getInt(BUNDLE_TYPE_KEY), System.currentTimeMillis());

                for (MapEventListener listener : mListeners) {
                    switch (resultData.getInt(BUNDLE_TYPE_KEY)) {
                        case EVENT_onLayerAdded:
                            listener.onLayerAdded(resultData.getInt(BUNDLE_ID_KEY));
                            break;
                        case EVENT_onLayerDeleted:
                            listener.onLayerDeleted(resultData.getInt(BUNDLE_ID_KEY));
                            break;
                        case EVENT_onLayerChanged:
                            listener.onLayerChanged(resultData.getInt(BUNDLE_ID_KEY));
                            break;
                        case EVENT_onExtentChanged:
                            listener.onExtentChanged(
                                    resultData.getFloat(BUNDLE_ZOOM_KEY), new GeoPoint(
                                            resultData.getDouble(BUNDLE_X_KEY),
                                            resultData.getDouble(BUNDLE_Y_KEY)));
                            break;
                        case EVENT_onLayerDrawFinished:
                            listener.onLayerDrawFinished(
                                    resultData.getInt(BUNDLE_ID_KEY),
                                    resultData.getFloat(BUNDLE_DONE_KEY));
                            break;
                        case EVENT_onLayersReordered:
                            listener.onLayersReordered();
                            break;
                        case EVENT_onLayerDrawStarted:
                            listener.onLayerDrawStarted();
                            break;
                    }
                }
            }
        };
    }


    public void freeze()
    {
        mFreeze = true;
    }


    public void thaw()
    {
        mFreeze = false;
    }
}