package com.here.mobility.sdk.reactnative.demand;

import android.support.annotation.NonNull;
import android.support.annotation.Nullable;

import com.facebook.react.bridge.Callback;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.modules.core.DeviceEventManagerModule;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonObject;
import com.here.mobility.sdk.demand.CreateRideRequest;
import com.here.mobility.sdk.demand.DemandClient;
import com.here.mobility.sdk.demand.Ride;
import com.here.mobility.sdk.demand.RideLocation;
import com.here.mobility.sdk.demand.RideOffersRequest;
import com.here.mobility.sdk.demand.RideQuery;
import com.here.mobility.sdk.demand.RideStatusLog;
import com.here.mobility.sdk.demand.RideWaypoints;
import com.here.mobility.sdk.reactnative.demand.models.RideLocationChange;
import com.here.mobility.sdk.reactnative.demand.models.RideStatusUpdate;

import static com.here.mobility.sdk.reactnative.demand.util.HereSDKModelConverter.bookingConstraints;
import static com.here.mobility.sdk.reactnative.demand.util.HereSDKModelConverter.hasValidKey;
import static com.here.mobility.sdk.reactnative.demand.util.HereSDKModelConverter.passengerDetails;
import static com.here.mobility.sdk.reactnative.demand.util.HereSDKModelConverter.priceRange;
import static com.here.mobility.sdk.reactnative.demand.util.HereSDKModelConverter.ridePreferences;
import static com.here.mobility.sdk.reactnative.demand.util.HereSDKModelConverter.rideQuery;
import static com.here.mobility.sdk.reactnative.demand.util.HereSDKModelConverter.rideWaypoints;
import static com.here.mobility.sdk.reactnative.demand.util.HereSDKModelConverter.transitOptions;


public class HereSDKDemandModule extends ReactContextBaseJavaModule {


    private static final String EVENT_RIDE_STATUS_CHANGED = "ride_status_changed";
    private static final String EVENT_RIDE_LOCATION_CHANGED = "ride_location_changed";
    private static final String EVENT_RIDE_UPDATES_ERROR = "ride_updates_error";


    private DemandClient demandClient;


    @NonNull
    private Gson gson;


    @Nullable
    private DemandClient.RideUpdateListener rideUpdatesListener;


    public HereSDKDemandModule(@NonNull ReactApplicationContext reactContext) {
        super(reactContext);
        demandClient = DemandClient.newInstance(reactContext);
        gson = new GsonBuilder().create();
    }


    @Override
    @NonNull
    public String getName() {
        return "HereSDKDemand";
    }


    @ReactMethod
    public void getRideOffers(@NonNull ReadableMap params, @NonNull Callback callback){

       //RideWaypoints
       RideWaypoints rideWaypoints = rideWaypoints(params.getMap("rideWaypoints"));

       //RideOffers request build.
       RideOffersRequest.Builder offersRequestBuilder = RideOffersRequest.builder()
               .setRideWaypoints(rideWaypoints);

       //Append optional params.
       if(hasValidKey(params, "priceRange")){
           offersRequestBuilder.setPriceRange(priceRange(params.getMap("priceRange")));
       }
       if(hasValidKey(params, "constraints")){
           offersRequestBuilder.setConstraints(bookingConstraints(params.getMap("constraints")));
       }
       if (hasValidKey(params, "prebookPickupTime")) {
           offersRequestBuilder.setPrebookPickupTime((long) params.getDouble("prebookPickupTime"));
       }
       if (hasValidKey(params, "sortType")) {
           offersRequestBuilder.setSortType(RideOffersRequest.SortType.valueOf(params.getString("sortType")));
       }
       if (hasValidKey(params, "passengerNote")) {
           offersRequestBuilder.setPassengerNote(params.getString("passengerNote"));
       }
       if (hasValidKey(params, "transitOptions")) {
           transitOptions(params.getMap("transitOptions"));
       }

       demandClient.getRideOffers(offersRequestBuilder.build()).
                            registerListener(new HereSDKBridgeListener<>(callback, gson));
    }


    @ReactMethod
    public void createRide(@NonNull String rideOfferId,
                           @NonNull ReadableMap passengerDetails,
                           @Nullable ReadableMap ridePreferences,
                           @NonNull Callback callback) {
        CreateRideRequest.Builder rideRequest =
                CreateRideRequest.builder(rideOfferId, passengerDetails(passengerDetails));

        if (ridePreferences != null) {
            rideRequest.setRidePreferences(ridePreferences(ridePreferences));
        }

        demandClient.createRide(rideRequest.build()).
                registerListener(new HereSDKBridgeListener<>(callback, gson));
    }


    @ReactMethod
    public void getRide(@NonNull String rideId,
                        @NonNull Callback callback) {
        demandClient.getRide(rideId).
                registerListener(new HereSDKBridgeListener<>(callback, gson));
    }


    @ReactMethod
    public void cancelRide(@NonNull String rideId,
                           @NonNull String reason,
                           @NonNull Callback callback) {
        demandClient.cancelRide(rideId, reason).
                registerListener(new HereSDKBridgeListener<>(callback, gson));
    }


    @ReactMethod
    public void getRides(
            @Nullable ReadableMap params,
            @NonNull Callback callback) {

        demandClient.getRides(rideQuery(params)).
                registerListener(new HereSDKBridgeListener<>(callback, gson));
    }


    @ReactMethod
    public void registerForRidesUpdates() {

        if(rideUpdatesListener != null){
            demandClient.unregisterFromRideUpdates(rideUpdatesListener);
        }

        final DeviceEventManagerModule.RCTDeviceEventEmitter eventEmitter = getReactApplicationContext()
                .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class);
        rideUpdatesListener = new DemandClient.RideUpdateListener() {
            @Override
            public void onRideStatusChanged(@NonNull Ride ride, @NonNull RideStatusLog rideStatusLog) {

                // It's very important decoding once to provide a valid json object to js.
                // To do so make sure we calling Gson.toJson once.
                RideStatusUpdate rideStatusUpdate = new RideStatusUpdate(ride, rideStatusLog);
                String update = gson.toJson(rideStatusUpdate);
                eventEmitter.emit(EVENT_RIDE_STATUS_CHANGED, update);
            }

            @Override
            public void onRideLocationChanged(@NonNull Ride ride, @NonNull RideLocation rideLocation) {

                // It's very important decoding once to provide a valid json object to js.
                // To do so make sure we calling Gson.toJson once.
                RideLocationChange rideLocationChange = new RideLocationChange(ride, rideLocation);
                String update = gson.toJson(rideLocationChange);
                eventEmitter.emit(EVENT_RIDE_LOCATION_CHANGED, update);
            }

            @Override
            public void onErrorOccurred(@NonNull Throwable throwable) {
                JsonObject error = new JsonObject();
                error.addProperty("reason", throwable.getMessage());
                final String json = error.toString();
                eventEmitter.emit(EVENT_RIDE_UPDATES_ERROR, json);
            }
        };
        demandClient.registerToRideUpdates(rideUpdatesListener);
    }


    @ReactMethod
    public void unregisterFromRidesUpdates() {

        if (rideUpdatesListener != null) {
            demandClient.unregisterFromRideUpdates(rideUpdatesListener);
        }
        rideUpdatesListener = null;
    }
}