/*
 * Copyright 2013 Hannes Janetzek
 * Copyright 2016 Andrey Novikov
 * Copyright 2020 devemux86
 *
 * This file is part of the OpenScienceMap project (http://www.opensciencemap.org).
 *
 * This program is free software: you can redistribute it and/or modify it under the
 * terms of the GNU Lesser General 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 General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License along with
 * this program. If not, see <http://www.gnu.org/licenses/>.
 */
package org.oscim.layers.tile;

import org.oscim.core.MapPosition;
import org.oscim.event.Event;
import org.oscim.layers.Layer;
import org.oscim.map.Map;
import org.oscim.map.Map.UpdateListener;
import org.oscim.tiling.TileSource;

public abstract class TileLayer extends Layer implements UpdateListener {

    private int mNumLoaders = 4;

    /**
     * TileManager responsible for adding visible tiles
     * to load queue and managing in-memory tile cache.
     */
    protected final TileManager mTileManager;

    protected TileLoader[] mTileLoader;

    protected TileSource mTileSource;

    public TileLayer(Map map, TileManager tileManager, TileRenderer renderer) {
        super(map);
        renderer.setTileManager(tileManager);

        mTileManager = tileManager;
        mRenderer = renderer;

    }

    public TileLayer(Map map, TileManager tileManager) {
        super(map);
        mTileManager = tileManager;
    }

    protected void setRenderer(TileRenderer renderer) {
        renderer.setTileManager(mTileManager);
        mRenderer = renderer;
    }

    protected abstract TileLoader createLoader();

    public TileRenderer tileRenderer() {
        return (TileRenderer) mRenderer;
    }

    protected void initLoader(int numLoaders) {
        mTileLoader = new TileLoader[numLoaders];

        for (int i = 0; i < numLoaders; i++) {
            mTileLoader[i] = createLoader();
            mTileLoader[i].start();
        }
    }

    /**
     * Get number of loader threads. Default is 4.
     */
    protected int getNumLoaders() {
        return mNumLoaders;
    }

    /**
     * Set number of loader threads. Should be called before attaching layer to map.
     */
    public void setNumLoaders(int num) {
        mNumLoaders = num;
    }

    @Override
    public void onMapEvent(Event event, MapPosition mapPosition) {

        if (event == Map.CLEAR_EVENT) {
            /* sync with TileRenderer */
            synchronized (mRenderer) {
                tileRenderer().clearTiles();
                mTileManager.init();
            }

            if (mTileManager.update(mapPosition))
                notifyLoaders();

        } else if (event == Map.POSITION_EVENT) {
            if (mTileManager.update(mapPosition))
                notifyLoaders();
        }
    }

    @Override
    public void onDetach() {
        for (TileLoader loader : mTileLoader) {
            loader.pause();
            loader.finish();
            loader.dispose();
        }
        if (mTileSource != null)
            mTileSource.close();
    }

    void notifyLoaders() {
        for (TileLoader loader : mTileLoader)
            loader.go();
    }

    protected void pauseLoaders(boolean wait) {
        for (TileLoader loader : mTileLoader) {
            loader.cancel();

            if (!loader.isPausing())
                loader.pause();
        }

        if (!wait)
            return;

        for (TileLoader loader : mTileLoader) {
            if (!loader.isPausing())
                loader.awaitPausing();
        }
    }

    protected void resumeLoaders() {
        for (TileLoader loader : mTileLoader)
            loader.proceed();
    }

    public TileManager getManager() {
        return mTileManager;
    }

    public TileSource getTileSource() {
        return mTileSource;
    }
}
