/*
 * Copyright 2016 ANTONIO CARLON
 *
 * 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.antoniocarlon.richmaps;

import android.graphics.Bitmap;
import android.graphics.Color;
import android.view.View;

import com.google.android.gms.maps.GoogleMap;
import com.google.android.gms.maps.Projection;
import com.google.android.gms.maps.model.BitmapDescriptorFactory;
import com.google.android.gms.maps.model.CameraPosition;
import com.google.android.gms.maps.model.GroundOverlay;
import com.google.android.gms.maps.model.GroundOverlayOptions;
import com.google.maps.android.SphericalUtil;

import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.SortedMap;
import java.util.TreeMap;

/**
 * Represents a layer on the map that will be drawn using rich symbology.
 * It's drawn as a GroundOverlay.
 */
public class RichLayer {
    private static final float MINIMUM_ZOOM_LEVEL = 5f;
    private View view;
    private GoogleMap map;
    private float zIndex = 0;
    private GroundOverlay overlay;
    private SortedMap<Integer, List<RichShape>> shapes = new TreeMap<>();
    private Bitmap bitmap;
    private int paddingLeft;
    private int paddingTop;
    private int paddingRight;
    private int paddingBottom;

    private RichLayer(final View view, final GoogleMap map, final float zIndex,
                      final int paddingLeft, final int paddingTop,
                      final int paddingRight, final int paddingBottom) {
        if (view == null || map == null) {
            throw new IllegalArgumentException("View and GoogleMap cannot be null");
        }

        this.view = view;
        this.map = map;
        this.zIndex = zIndex;
        this.paddingLeft = paddingLeft;
        this.paddingTop = paddingTop;
        this.paddingRight = paddingRight;
        this.paddingBottom = paddingBottom;

        map.getUiSettings().setTiltGesturesEnabled(false); // For now, tilt gestures are not allowed when using RichLayer
    }

    public void refresh() {
        CameraPosition cameraPosition = map.getCameraPosition();
        if (cameraPosition.zoom >= MINIMUM_ZOOM_LEVEL) {
            Projection projection = map.getProjection();

            prepareBitmap();
            draw(bitmap, projection);

            float mapWidth = (float) SphericalUtil.computeDistanceBetween(
                    projection.getVisibleRegion().nearLeft,
                    projection.getVisibleRegion().nearRight);

            if (overlay == null) {
                GroundOverlayOptions background = new GroundOverlayOptions()
                        .image(BitmapDescriptorFactory.fromBitmap(bitmap))
                        .position(cameraPosition.target, mapWidth)
                        .bearing(cameraPosition.bearing)
                        .zIndex(zIndex);
                overlay = map.addGroundOverlay(background);
            } else {
                overlay.setImage(BitmapDescriptorFactory.fromBitmap(bitmap));
                overlay.setPosition(cameraPosition.target);
                overlay.setDimensions(mapWidth);
                overlay.setBearing(cameraPosition.bearing);
            }
        } else {
            if (overlay != null) {
                overlay.remove();
                overlay = null;
            }
        }
    }

    public void addShape(final RichShape shape) {
        if (shape != null) {
            if (!shapes.containsKey(shape.getZIndex())) {
                shapes.put(shape.getZIndex(), new ArrayList<RichShape>());
            }

            List<RichShape> shapesZIndez = shapes.get(shape.getZIndex());
            shapesZIndez.add(shape);
        }
    }

    public void removeShape(final RichShape shape) {
        if (shape != null) {
            Set<Integer> zIndices = shapes.keySet();
            for (Integer zIndex : zIndices) {
                List<RichShape> shapesZIndex = shapes.get(zIndex);
                shapesZIndex.remove(shape);
            }
        }
    }

    private void prepareBitmap() {
        if (bitmap == null || bitmap.getWidth() != view.getWidth()
                || bitmap.getHeight() != view.getHeight()) {
            bitmap = Bitmap.createBitmap(view.getWidth(), view.getHeight(), Bitmap.Config.ARGB_8888);
        } else {
            bitmap.eraseColor(Color.TRANSPARENT);
        }
    }

    private Bitmap draw(final Bitmap bitmap, final Projection projection) {
        Set<Integer> zIndices = shapes.keySet();
        for (Integer zIndex : zIndices) {
            draw(bitmap, projection, shapes.get(zIndex));
        }

        return bitmap;
    }

    private Bitmap draw(final Bitmap bitmap, final Projection projection,
                        final List<RichShape> shapes) {
        for (RichShape shape : shapes) {
            shape.draw(bitmap, projection, paddingLeft, paddingTop, paddingRight, paddingBottom);
        }
        return bitmap;
    }

    public static class Builder {
        private View view;
        private GoogleMap map;
        private float zIndex = 0;
        private int paddingLeft = 0;
        private int paddingTop = 0;
        private int paddingRight = 0;
        private int paddingBottom = 0;

        public Builder(final View view, final GoogleMap map) {
            this.view = view;
            this.map = map;
        }

        public Builder zIndex(final float zIndex) {
            this.zIndex = zIndex;
            return this;
        }

        public Builder padding(final int top, final int bottom) {
            this.paddingTop = top;
            this.paddingBottom = bottom;
            return this;
        }

        public RichLayer build() {
            return new RichLayer(view, map, zIndex,
                    paddingLeft, paddingTop, paddingRight, paddingBottom);
        }
    }
}