package tech.zafrani.companionforpubg.maps.actions;

import android.support.annotation.DrawableRes;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v4.content.ContextCompat;

import com.google.android.gms.maps.GoogleMap;
import com.google.android.gms.maps.model.LatLng;
import com.google.android.gms.maps.model.Marker;
import com.google.android.gms.maps.model.MarkerOptions;
import com.google.android.gms.maps.model.Polyline;
import com.google.android.gms.maps.model.PolylineOptions;

import tech.zafrani.companionforpubg.maps.GoogleMapController;
import tech.zafrani.companionforpubg.R;

public class DistanceAction extends Action
        implements
        GoogleMap.OnMapClickListener {

    private final static int SECONDS_PER_METER = 12;
    private final static double DISTANCE_PER_METER = 4.5;

    private static double getDistance(final double x1,
                                      final double x2,
                                      final double y1,
                                      final double y2) {
        final double x = (x2 - x1) * (x2 - x1);
        final double y = (y2 - y1) * (y2 - y1);
        return Math.sqrt((x + y));
    }

    @NonNull
    private final GoogleMapController mapController;

    @NonNull
    private final String snippet;

    private final int colorAccent;

    @Nullable
    private LatLngMarker origin = null;

    @Nullable
    private LatLngMarker destination = null;

    @Nullable
    private Polyline polyline = null;


    public DistanceAction(@NonNull final GoogleMapController mapController) {
        this.mapController = mapController;
        this.snippet = mapController.getContext().getString(R.string.distance_action_label_snippet);
        this.colorAccent = ContextCompat.getColor(mapController.getContext(), R.color.accent);
    }

    //region Actions
    @Override
    protected void onToggleAction() {
        release();
        if (shouldShow()) {
            this.mapController.setOnMapClickListener(this);
        } else {
            this.mapController.setOnMapClickListener(null);
        }
    }

    @DrawableRes
    @Override
    protected int getMarkerIconRes() {
        return R.drawable.marker_run;
    }

    @Override
    public void release() {
        releaseDestination();
        releaseOrigin();
        releasePolyline();
    }
    //endregion

    //region OnMapClickListener
    @Override
    public void onMapClick(final LatLng latLng) {
        if (this.origin == null || this.destination != null) {
            release();
            setOrigin(latLng);
        } else {
            setDestination(latLng);
        }
    }
    //endregion

    //region methods

    /**
     * Set the first selected point.
     *
     * @param origin Position on {@link GoogleMap} user clicked.
     */
    private void setOrigin(@Nullable final LatLng origin) {
        releasePolyline();
        releaseOrigin();
        if (origin != null) {
            this.origin = new LatLngMarker(origin, addMarker(origin));
        }
    }

    /**
     * Set the second selected point.
     *
     * @param destination Position on {@link GoogleMap} user clicked.
     */
    private void setDestination(@Nullable final LatLng destination) {
        releasePolyline();
        releaseDestination();
        if (destination != null) {
            this.destination = new LatLngMarker(destination, addMarker(destination));
            addPolyline();
        }
    }

    /**
     * Helper function.
     *
     * @param latLng position on {@link GoogleMap} to create a marker.
     * @return marker on map.
     */
    @NonNull
    private Marker addMarker(@NonNull final LatLng latLng) {
        final MarkerOptions options = createMarkerOptions();
        options.position(latLng);
        return this.mapController.addMarker(options);
    }

    /**
     * If an origin and destination exist this will draw a line between them and
     * render the time it takes to reach the {@link DistanceAction#destination}
     */
    private void addPolyline() {
        if (this.origin == null || this.destination == null) {
            return;
        }
        releasePolyline();
        this.destination.marker.setTitle(getTitleForDistance(getDistance()));
        this.destination.marker.setSnippet(this.snippet);
        this.destination.marker.showInfoWindow();
        final PolylineOptions polylineOptions = new PolylineOptions();
        polylineOptions.add(this.origin.latLng);
        polylineOptions.add(this.destination.latLng);
        polylineOptions.width(5);
        polylineOptions.color(this.colorAccent);
        polylineOptions.zIndex(1000);
        this.polyline = this.mapController.addPolyline(polylineOptions);
    }

    /**
     * Remove any references that may cause a leak.
     */
    private void releaseDestination() {
        if (this.destination != null) {
            this.destination.release();
            this.destination = null;
        }
    }

    /**
     * Remove any references that may cause a leak.
     */
    private void releasePolyline() {
        if (this.polyline != null) {
            this.polyline.remove();
            this.polyline = null;
        }
    }

    /**
     * Remove any references that may cause a leak.
     */
    private void releaseOrigin() {
        if (this.origin != null) {
            this.origin.release();
            this.origin = null;
        }
    }

    /**
     * @return if {@link DistanceAction#origin} and {@link DistanceAction#destination} exist will
     * return the distance between them. If one or both are missing will return -1.
     */
    private double getDistance() {
        if (this.origin == null || this.destination == null) {
            return -1;
        }
        return DistanceAction.getDistance(this.origin.latLng.latitude,
                                          this.destination.latLng.latitude,
                                          this.origin.latLng.longitude,
                                          this.destination.latLng.longitude);
    }

    /**
     * @param distance distance between {@link DistanceAction#origin} and {@link DistanceAction#destination}.
     * @return String to display in the {@link DistanceAction#destination}'s {@link LatLngMarker#marker}.
     */
    private String getTitleForDistance(final double distance) {
        if (distance == -1) {
            // Shouldn't happen. Just in case.
            return this.mapController.getContext().getString(R.string.distance_action_label_title_failed);
        }
        final double meters = distance / DISTANCE_PER_METER;
        final int seconds = (int) (meters * SECONDS_PER_METER);
        if (seconds > 60) {
            final int minutes = (seconds / 60);
            final int remainder = (seconds % 60);
            //todo use strings.xml
            if (remainder == 0) {
                return "~" + minutes + " minutes";
            } else {
                return "~" + minutes + " minutes and " + remainder + " seconds";
            }
        }
        return "~" + seconds + " seconds";
    }
    //endregion

    /**
     * Helper class to hold final references of a positions {@link LatLng} and {@link Marker}.
     */
    private class LatLngMarker {
        @NonNull
        private LatLng latLng;
        @NonNull
        private Marker marker;

        private LatLngMarker(@NonNull LatLng latLng,
                             @NonNull Marker marker) {
            this.latLng = latLng;
            this.marker = marker;
        }

        /**
         * Remove the marker from the map.
         */
        private void release() {
            marker.remove();
        }
    }
}