package com.ctpodp;

import android.util.Log;

import com.ctpodp.objects.ProcessedInstruction;
import com.ctpodp.objects.fromReact.Geojson.GeoJson;
import com.ctpodp.objects.fromReact.Geojson.GeoJsonGeometry;
import com.ctpodp.objects.fromReact.Geojson.GeojsonPathObj;
import com.ctpodp.objects.fromReact.Geojson.GeojsonPathWaypointObj;
import com.facebook.react.bridge.Arguments;
import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.bridge.WritableArray;
import com.facebook.react.bridge.WritableMap;
import com.jibestream.jmapandroidsdk.astar.ASNode;
import com.jibestream.jmapandroidsdk.astar.PathPerFloor;
import com.jibestream.jmapandroidsdk.components.ActiveVenue;
import com.jibestream.jmapandroidsdk.components.Associations;
import com.jibestream.jmapandroidsdk.components.Destination;
import com.jibestream.jmapandroidsdk.components.Waypoint;
import com.jibestream.jmapandroidsdk.jcontroller.JController;
import com.jibestream.jmapandroidsdk.main.PointD;
import com.jibestream.jmapandroidsdk.main.Utilities;
import com.jibestream.jmapandroidsdk.rendering_engine.drawables.JShapeDrawable;
import com.jibestream.navigationkit.instructionfactory.Instruction;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.List;

public class PathUtilities {

  //{
  //                distance: <total distance in meters>,
  //                duration: <total duration in seconds>,
  //                startTime: <unix timestamp in milliseconds>,
  //                endTime: <unix timestamp in milliseconds>,
  //                legGeometry: <GeoJson>,
  //                steps: <waypoints> {
  //                                distance: <distance in meters for this step/waypoint>,
  //                                directions: <actual turn-by-turn instruction, in feet>,
  //                                lat: <latitude>,
  //                                lon: <longitude>
  //                }
  //}
  public static GeojsonPathObj createGeojsonPath(PathPerFloor[] pathPerFloors, ProcessedInstruction[] instructions, ActiveVenue venue, double movementSpeedMps) {
    GeojsonPathObj path = new GeojsonPathObj();

    List<double[]> geometry = new ArrayList<double[]>();
    if (pathPerFloors.length > 0) {
      if (pathPerFloors[0].points.length > 0) {
        ASNode node = pathPerFloors[0].points[0];
        PointD convertPoint = new PointD(node.x, node.y);
        Utilities.convertLocalToEpsg4326(convertPoint, venue);
        geometry.add(new double[]{convertPoint.x, convertPoint.y});
      }
    }

    double totalDistance = 0;
    List<GeojsonPathWaypointObj> waypoints = new ArrayList<GeojsonPathWaypointObj>();
    for (ProcessedInstruction instruction : instructions) {
      GeojsonPathWaypointObj waypoint = new GeojsonPathWaypointObj();
      waypoint.directions = instruction.fullText;

      PointD convertPoint = new PointD(instruction.cornerLocation[0], instruction.cornerLocation[1]);
      Utilities.convertLocalToEpsg4326(convertPoint, venue);
      geometry.add(new double[]{convertPoint.x, convertPoint.y});
      waypoint.lat = convertPoint.y;
      waypoint.lon = convertPoint.x;
      waypoint.distance = instruction.distance;
      totalDistance += waypoint.distance;
      waypoints.add(waypoint);
    }
    double[][] legGeometry = new double[geometry.size()][];
    geometry.toArray(legGeometry);
    path.legGeometry = new GeoJson(new GeoJsonGeometry(legGeometry));

    GeojsonPathWaypointObj[] steps = new GeojsonPathWaypointObj[waypoints.size()];
    waypoints.toArray(steps);
    path.steps = steps;
    path.distance = totalDistance;
    path.duration = path.distance/movementSpeedMps;
    path.startTime = System.currentTimeMillis() / 1000;
    path.endTime = path.startTime + (long) path.duration;

    return path;
  }

  private static double getDistance(Double[] a, Double[] b) {
    return Math.sqrt(Math.pow(a[0] - b[0], 2) + Math.pow(a[1] - b[1], 2));
  }

  public static ProcessedInstruction[] processInstructions(Instruction[] instructions, ActiveVenue venue, String unit, float mmPerPixel) {
    List<ProcessedInstruction> processedInstructions = new ArrayList<>();
    for (int i = 0; i < instructions.length; i++) {
      Instruction instruction = instructions[i];
      ProcessedInstruction processedInstruction = new ProcessedInstruction();
      if (instruction.getDecisionPoint() != null) {
        processedInstruction.wp = instruction.getDecisionPoint().id;
        processedInstruction.cornerLocation = instruction.getDecisionPoint().getCoordinates();
      } else {
        processedInstruction.wp = instruction.getCompletionPoint().id;
        processedInstruction.cornerLocation = instruction.getCompletionPoint().getCoordinates();
      }
      Log.d("DISTANCE", instruction.getText() + ", " + instruction.getPixelDistance());
      Float distanceMeters = instruction.getPixelDistance() * mmPerPixel / 1000;
      if (distanceMeters == 0 && processedInstructions.size() != 0) {
        distanceMeters = (float) getDistance(processedInstructions.get(processedInstructions.size() - 1).cornerLocation, processedInstruction.cornerLocation) * mmPerPixel / 1000;
      }
      Float distance = distanceMeters;
      String units = "";

      switch(unit) {
        case "FEET":
          distance *= 3.28084F;
          units = Math.ceil(distance) == 1 ? " foot" : " feet";
          break;
        case "METERS":
        default:
          units = Math.ceil(distance) == 1 ? " meter" : " meters";
          break;
      }
      processedInstruction.distance = Double.valueOf(distanceMeters);
      processedInstruction.remainingDistance = Double.valueOf(distanceMeters);
      processedInstruction.direction = instruction.getDirection();

      String prepend = "";
      if (instruction.getDirection().equals("Left") || instruction.getDirection().equals("Right")) {
        prepend = "Turn ";
      }
      processedInstruction.prepend = prepend;

      if (instruction.getSurroundingElements() != null &&
        instruction.getSurroundingElements().getShapes() != null &&
        instruction.getSurroundingElements().getShapes().length > 0) {
        JShapeDrawable shape = instruction.getSurroundingElements().getShapes()[0].getShape();

        //Get data-unit meta data which is associated to units
        Object[] dataUnits = shape.getMetaData("data-unit");

        //Make sure shape has destination association
        if (dataUnits != null && dataUnits.length > 0) {
          int destinationId = (int) dataUnits[0];

          //Get destination object from Id
          Destination destination = venue.getDestinations().getById(destinationId);

          //Get destination name
          String destinationName = destination.getName();

          if (destinationName != null) {
            processedInstruction.destinationName = destinationName;

            processedInstruction.dest = destination.id;
            processedInstruction.fullText = instruction.getText() + " at " + destinationName + " in " + (int)Math.ceil(distance) + units;

          } else {
            processedInstruction.dest = destination.id;
            processedInstruction.fullText = instruction.getText() + " in " + (int)Math.ceil(distance) + units;
          }
        } else {
          processedInstruction.destinationName = "";
          processedInstruction.dest = null;
          processedInstruction.fullText = instruction.getText() + " in " + (int)Math.ceil(distance) + units;
        }
      }
      processedInstructions.add(processedInstruction);
    }
    return processedInstructions.toArray(new ProcessedInstruction[processedInstructions.size()]);
  }

  public static WritableArray getMapFromPath(JController controller, PathPerFloor[] pathPerFloors) {
    WritableArray pathArr = ReactUtilities.classToArray(pathPerFloors);
    WritableArray pathArrW = Arguments.createArray();
    for (int i = 0; i < pathArr.size(); i++) {
      ReadableMap path = pathArr.getMap(i);

      WritableMap pathW = Arguments.createMap();
      pathW.putInt("mapId", path.getInt("mapId"));
      pathW.putMap("mover", path.getMap("mover"));
      WritableArray pointsW = Arguments.createArray();
      pathW.putDouble("cost", path.getDouble("cost"));
      pathW.putDouble("pixelDistance", path.getDouble("pixelDistance"));
      pathW.putDouble("mmDistance", path.getDouble("mmDistance"));

      for (int j = 0; j < path.getArray("points").size(); j++) {

        ReadableMap point = path.getArray("points").getMap(j);
        WritableMap pointW = Arguments.createMap();

        pointW.putInt("id", point.getInt("id"));
        pointW.putInt("mapId", point.getInt("mapId"));
        pointW.putDouble("x", point.getDouble("x"));
        pointW.putDouble("y", point.getDouble("y"));
        pointW.putDouble("z", point.getDouble("z"));

        int id = point.getInt("id");
        Waypoint waypoint = Arrays.stream(controller.getActiveVenue().getMaps().getAllWaypoints()).filter(wp -> wp.id == id).findFirst().get();
        Associations associations = waypoint.getAssociations();
        WritableMap assocaitionsMap = ReactUtilities.classToMap(associations);

        pointW.putMap("associations", assocaitionsMap);
        pointsW.pushMap(pointW);
      }

      pathW.putArray("points", pointsW);
      pathArrW.pushMap(pathW);
    }
    return pathArrW;
  }
}
