<?php

class Route {

    /*
        savvy=> select id,name from transport_providers;
         id |     name
        ----+---------------
          1 | Uber
          2 | Lyft
          3 | Grab
          4 | ComfortDelGro
          5 | GOJEK
          6 | Turo
          7 | Getaround
         10 | GrabWheels
         11 | BART
         12 | Muni
         13 | LTA
        (11 rows)
    */
    public static function getTransportRoute($db, $origin, $destination, $option, $trip) {
        $route = [];
        switch ($option["transport_provider_id"]) {
            case 1:
            case 2:
            case 3:
            case 4:
            case 5:
            case 8:
                list($option, $trip, $route) = Route::drivingDirections($db, $origin, $destination, $option, $trip);
                break;
            case 11: // BART
                list($option, $trip, $route) = Route::bartDirections($db, $origin, $destination, $option, $trip);
                break;
            case 12: // Muni
                list($option, $trip, $route) = Route::muniDirections($db, $origin, $destination, $option, $trip);
                break;
            case 13: // LTA
                list($option, $trip, $route) = Route::ltaDirections($db, $origin, $destination, $option, $trip);
                break;
            case 14: // MARTA
                list($option, $trip, $route) = Route::martaDirections($db, $origin, $destination, $option, $trip);
                break;
        }
        error_log(json_encode($trip));
        return [$option, $trip, $route];
    }

    public static function drivingDirections($db, $origin, $destination, $option, $trip) {
        list($option, $trip, $route) = Route::processRideshareOption($db, $option, $origin, $destination, $trip);
        $option["travel_time"] = (int)$trip["duration"];
        $option["travel_distance"] = (int)$trip["distance"];
        $trip["source"] = strtolower($option["name"]);
        return [$option, $trip, $route];
    }
    
    public static function drivingDirectionsOriginal($db, $origin, $destination, $option, $trip) {
        $options = [];
        $trip = [];
        $option["travel_time"] = 0;
        $option["travel_distance"] = 0;
        $fromLat = $origin['lat'];
        $fromLng = $origin['lng'];
        $toLat = $destination['lat'];
        $toLng = $destination['lng'];
        // This is paranoid redudant check, but since the function is "public"...
        if ($fromLat==null || $fromLng==null || $toLat==null || $toLng==null
        || ($fromLat==0 && $fromLng==0) || ($toLat==0 && $toLng==0)) {
            throw new Exception('Invalid origin and/or destination coordinates');
        }
        list($res, $err) = Route::serviceRoute($db, $fromLat, $fromLng, $toLat, $toLng);
        if ($err!=NULL || !is_array($res) || !isset($res["routes"]) || count($res["routes"])<1) {
            $options['error'] = $err!=NULL ? $err : 'No available routes';
            return [$option, $trip, $options];
        }
        $options["route_overlay"] = [];
        $travel_time = PHP_INT_MAX;
        $travel_distance = PHP_INT_MAX;
        foreach ($res["routes"] as $route) {
            $r = Route::processRoute($route);
            if ($travel_time>$r['duration']) {
                $travel_time = $r['duration'];
                $travel_distance = $r['distance'];
            } 
            $options["route_overlay"][] = $r;
        }
        $options["travel_time"] = $travel_time==PHP_INT_MAX ? NULL : $travel_time;
        $options["travel_distance"] = $travel_distance==PHP_INT_MAX ? NULL : $travel_distance;

        $option["travel_time"] = (int)$options["travel_time"];
        $option["travel_distance"] = (int)$options["travel_distance"];

        //https://developers.google.com/maps/documentation/utilities/polylinealgorithm
        //overview_polyline
        //bounds contains the viewport bounding box of the overview_polyline.
        return [$option, $trip, $options];
    }

    public static function bartDirections($db, $origin, $destination, $option, $trip) {
        $route = [];

        $option["travel_time"] = 0;
        $option["travel_distance"] = 0;

        $route["travel_time"] = 0;
        $route["travel_distance"] = 0;
        $route["route_overlay"] = [];

        $country = $origin["country"];
        $member_id = $option["member_id"];

        // New directions
        $tripService = Route::tripService($trip["id"], $country, 'BART');
            
        //error_log(json_encode($tripService));
        if (is_array($tripService) && isset($tripService["travel_date"])) {
            $tripService["member_id"] = $member_id; // Pass along
            $tripService["country"] = $country;
            $tripService = MultiModalFilter::byDuration($tripService);
            //$tripService = MultiModalFilter::byCost($tripService);
            $trip = $tripService;

            $option["travel_time"] = (int)$trip["options"]["legs"][0]["duration"];
            $option["travel_distance"] = (int)$trip["options"]["legs"][0]["distance"];

            $route = Route::routeFromTrip($db, $origin, $destination, $option, $trip);
        }        
        return [$option, $trip, $route];
    }

    public static function muniDirections($db, $origin, $destination, $option, $trip) {
        $route = [];

        $option["travel_time"] = 0;
        $option["travel_distance"] = 0;
        $country = $origin["country"];
        $member_id = $option["member_id"];

        // New directions
        $tripService = Route::tripService($trip["id"], $country, 'MUNI');
            
        //error_log(json_encode($tripService));
        if (is_array($tripService) && isset($tripService["travel_date"])) {
            $tripService["member_id"] = $member_id; // Pass along
            $tripService["country"] = $country;

            $tripService = MultiModalFilter::byDuration($tripService);
            //$tripService = MultiModalFilter::byCost($tripService);
            $trip = $tripService;

            $option["travel_time"] = (int)$trip["options"]["legs"][0]["duration"];
            $option["travel_distance"] = (int)$trip["options"]["legs"][0]["distance"];

            $route = Route::routeFromTrip($db, $origin, $destination, $option, $trip);
        }        
        return [$option, $trip, $route];
    }

    public static function ltaDirections($db, $origin, $destination, $option, $trip) {
        $route = [];

        $option["travel_time"] = 0;
        $option["travel_distance"] = 0;
        $country = $origin["country"];
        $member_id = $option["member_id"];

        // New directions
        $tripService = Route::tripService($trip["id"], $country, 'LTA');
            
        //error_log(json_encode($tripService));
        if (is_array($tripService) && isset($tripService["travel_date"])) {
            $tripService["member_id"] = $member_id; // Pass along
            $tripService["country"] = $country;

            $tripService = MultiModalFilter::byDuration($tripService);
            //$tripService = MultiModalFilter::byCost($tripService);
            $trip = $tripService;

            $option["travel_time"] = (int)$trip["options"]["legs"][0]["duration"];
            $option["travel_distance"] = (int)$trip["options"]["legs"][0]["distance"];

            $route = Route::routeFromTrip($db, $origin, $destination, $option, $trip);
        }
        return [$option, $trip, $route];
    }

    public static function martaDirections($db, $origin, $destination, $option, $trip) {
        $route = [];

        $option["travel_time"] = 0;
        $option["travel_distance"] = 0;
        $country = $origin["country"];
        $member_id = $option["member_id"];

        // New directions
        $tripService = Route::tripService($trip["id"], $country, 'MARTA');
            
        //error_log(json_encode($tripService));
        if (is_array($tripService) && isset($tripService["travel_date"])) {
            $tripService["member_id"] = $member_id; // Pass along
            $tripService["country"] = $country;

            $tripService = MultiModalFilter::byDuration($tripService);
            $trip = $tripService;

            $option["travel_time"] = (int)$trip["options"]["legs"][0]["duration"];
            $option["travel_distance"] = (int)$trip["options"]["legs"][0]["distance"];

            $route = Route::routeFromTrip($db, $origin, $destination, $option, $trip);
        }        
        return [$option, $trip, $route];
    }

    public static function routeFromTrip($db, $origin, $destination, $option, $trip) {

        $leg = [
            "distance" => 0,
            "duration" => 0,
            "polyline" => NULL
        ];
        // Select leg
        foreach ($trip["options"]["legs"] as $leg) {
            break;
        }

        $route["travel_time"] = (int)$leg["duration"];
        $route["travel_distance"] = (int)$leg["distance"];

        // TODO: BART fix polyline!

        $route["route_overlay"][] = [
            "polyline" => [],
            "distance" =>  $leg["distance"],
            "duration" =>  $leg["duration"],
            "summary"  => "Generated option",
            "overview_polyline" => [
                "points" => Route::processPolyline($leg["polyline"])
            ],
            "bounds" => [
                "northeast" => [
                    "lat" => $origin["lat"],
                    "lng" => $origin["lng"]
                ],
                "southwest" => [
                    "lat" => $destination["lat"],
                    "lng" => $destination["lng"]
                ]
            ]
        ];
        return $route;
    }

    public static function checkValidRoute($db, $route) {
        // Basic input sanity check: route
        if (!is_array($route) || !array_key_exists('travel_time',$route) || !is_numeric($route['travel_time'])
        || !array_key_exists('travel_distance',$route) || !is_numeric($route['travel_distance'])
        || !array_key_exists('route_overlay',$route) || !is_array($route['route_overlay'])) {
            throw new Exception('Invalid transport route');
        }
    }

    public static function serviceRoute($db, $fromLat, $fromLng, $toLat, $toLng, $mode='driving',$waypoints=[]) {
        global $savvyext;
        $httpAuthToken = $savvyext->cfgReadChar('system.oauth2_token');
        $oauth2_url = $savvyext->cfgReadChar('system.oauth2_url');
        error_log("Route::serviceRoute(\$db, $fromLat, $fromLng, $toLat, $toLng, $mode, \$waypoints=[])");
        // Call geocoding service
        $data = http_build_query(
            array(
                'gps' => implode(",",array($fromLat, $fromLng, $toLat, $toLng)),
                'mode' => $mode,
                'waypoints' => implode(",",$waypoints)
            )
        );
        $url = $oauth2_url."route?" . $data;
        $opts = array(
            'http' => array(
                'method' => "GET",
                'timeout' => 60, /* 1 minute */
                'header'  => 
                    "Content-Type: application/x-www-form-urlencoded\r\n" .
                    "Accept: application/json\r\n" .
                    "Authorization: Server-Token ${httpAuthToken}\r\n",
            ),
            "ssl" => array(
              "verify_peer"=>false,
              "verify_peer_name"=>false,
            )
        );
        $context = stream_context_create($opts);
        $body = file_get_contents($url, false, $context);
        $geocoded = json_decode($body,true);
        if (is_array($geocoded) && is_array($geocoded["data"]) && !isset($geocoded["error"])) {
            return array($geocoded["data"], NULL);
        } else if (is_array($geocoded) && isset($geocoded["error"])) {
            $body = $geocoded["error"];
        }
        return array(NULL, "Routing service call error: ".$body);
    }

    public static function processRoute($route) {
        require_once('../common/Polyline.php');
        $result = [
            'polyline' => array(), 
            'distance' => NULL, 
            'duration' => NULL, 
            'summary' => $route["summary"], 
            'overview_polyline' => $route["overview_polyline"],
            'bounds' => $route["bounds"]
        ];
        $distance = 0;
        $duration = 0;
        foreach ($route["legs"] as $leg) {
            $distance += $leg["distance"]["value"];
            $duration += $leg["duration"]["value"];
            foreach ($leg["steps"] as $step) {
                $points = Polyline::decode($step["polyline"]["points"]);
                $result['polyline'] = array_merge($result['polyline'],Polyline::pair($points));
            }
        }
        $result['distance'] = $distance;
        $result['duration'] = $duration;
        return $result;
    }

    public static function processRideshareOption($db, $option, $origin, $destination, $trip) {
        error_log("Options::processRideshareOption(\$db, \$option, \$origin, \$destination, \$trip)");
        $route = [];
        $route["travel_time"] = 0;
        $route["travel_distance"] = 0;
        $route["route_overlay"] = [];
        $member_id = $option["member_id"];
        $country = $origin["country"]; 
        $rideshare = $option["name"];
        $transport_provider_id = $option["transport_provider_id"];
        if (array_key_exists("timeZoneId",$origin) && $origin["timeZoneId"]!="") {
            $tz = $origin["timeZoneId"];
        } else if (array_key_exists("timeZoneId",$destination) && $destination["timeZoneId"]!="") {
            $tz = $destination["timeZoneId"];
        } else {
            $tz = Options::getTimezoneById($db, $origin["timezone"], "Asia/Singapore");
        }
        //"trips"=>$trips;
        if (!is_array($trip) && count($trip)<1) {
            syslog(LOG_WARNING,"Invalid trip");
            return [$option, $trip, $route]; // No idea how to handle it
        }
        /*if (!array_key_exists('trip',$trip) || !is_array($trip['trip']) || count($trip['trip'])<10) {
            syslog(LOG_WARNING,"Invalid trip[0]!");
            return $trips; // No idea how to handle it
        } //*/
        $oldTrip = $trip; //['trip'];

        $origin['latitude'] = $origin['lat'];
        $origin['longitude'] = $origin['lng'];
        $destination['latitude'] = $destination['lat'];
        $destination['longitude'] = $destination['lng'];
        list($res, $err) = Route::serviceRoute($db, 
            $origin['lat'], $origin['lng'], 
            $destination['lat'], $destination['lng'], 'driving'
        );
        if ($err!=NULL || !is_array($res) || !isset($res["routes"]) || count($res["routes"])<1) {
            error_log('No available routes!');
            error_log($err);
            return [$option, $trip, $route]; // We cannot route driving!
        }
        $route_overlay = [];
        $travel_time = PHP_INT_MAX;
        $travel_distance = PHP_INT_MAX;
        foreach ($res["routes"] as $item) {
            $r = Route::processRoute($item);
            if ($travel_time>$r['duration']) {
                $travel_time = $r['duration'];
                $travel_distance = $r['distance'];
            } 
            $route_overlay[] = $r;
        }
        $oldTrip["duration"] = $travel_time==PHP_INT_MAX ? NULL : $travel_time;
        $oldTrip["distance"] = $travel_distance==PHP_INT_MAX ? NULL : $travel_distance;

        $route["travel_time"] = (int)$oldTrip["duration"];
        $route["travel_distance"] = (int)$oldTrip["distance"];
        $route["route_overlay"] = $route_overlay;
        /*
            $result = [
            'polyline' => array(), 
            'distance' => NULL, 
            'duration' => NULL, 
            'summary' => $route["summary"], 
            'overview_polyline' => $route["overview_polyline"],
            'bounds' => $route["bounds"]
        ];
        */
        //syslog(LOG_WARNING,json_encode($res));
        $route_0 = $res["routes"][0];
        $route_leg = $route_0["legs"][0];
        $route_leg_steps = $route_leg["steps"];
        $overview_polyline = Route::processPolyline($route_0["overview_polyline"]["points"]);

        $leg_steps = [];
        $distance = 0;
        $duration = 0;
        $location_start_lat = 0;
        $location_start_lng = 0;
        $location_end_lat = 0;
        $location_end_lng = 0;
        $polyline = [];
        $i = 0; $j=1; $n = count($route_leg_steps);
        for (; $i<$n; $i++) {
            $step = $route_leg_steps[$i];
            if ($step["travel_mode"] == "DRIVING") {
                $location_start_lat = $step["start_location"]["lat"];
                $location_start_lng = $step["start_location"]["lng"];
                break;
            }
            $leg_steps[] = [
                'distance' => $step["distance"]["value"],
                'duration' => $step["duration"]["value"],
                'html_instructions' => $step["html_instructions"],
                'location_end_lat' => $step["end_location"]["lat"],
                'location_end_lng' => $step["end_location"]["lng"],
                'location_start_lat' => $step["start_location"]["lat"],
                'location_start_lng' => $step["start_location"]["lng"],
                'polyline' => $step["polyline"]["points"], /*"yz|Fm_nxRi@e@_@P",*/
                'sid' => $j++,
                'travel_mode' => $step["travel_mode"]
            ];
        }
        for (;$i<$n; $i++) {
            $step = $route_leg_steps[$i];
            if ($step["travel_mode"] != "DRIVING") {
                break;
            }
            $distance += $step["distance"]["value"];
            $duration += $step["duration"]["value"];
            $location_end_lat = $step["end_location"]["lat"];
            $location_end_lng = $step["end_location"]["lng"];
            $polyline[] = $step["polyline"]["points"];
        }
        $leg_steps[] = [
            'distance' => $distance,
            'duration' => $duration,
            'fare_quote' => null,
            'fare_raw' => null,
            'html_instructions' => "Take ".$rideshare." to ".$destination["address"],
            'location_end_lat' => $location_end_lat,
            'location_end_lng' => $location_end_lng,
            'location_start_lat' => $location_start_lat,
            'location_start_lng' => $location_start_lng,
            'polyline' => Route::processPolyline($polyline),
            'quote_group_id' => null,
            'sid' => $j++,
            'travel_mode' => strtoupper($rideshare)
        ];
        for (;$i<$n; $i++) {
            $step = $route_leg_steps[$i];
            $leg_steps[] = [
                'distance' => $step["distance"]["value"],
                'duration' => $step["duration"]["value"],
                'html_instructions' => $step["html_instructions"],
                'location_end_lat' => $step["end_location"]["lat"],
                'location_end_lng' => $step["end_location"]["lng"],
                'location_start_lat' => $step["start_location"]["lat"],
                'location_start_lng' => $step["start_location"]["lng"],
                'polyline' => $step["polyline"]["points"], /*"yz|Fm_nxRi@e@_@P",*/
                'sid' => $j++,
                'travel_mode' => $step["travel_mode"]
            ];
        }

        $cost = null;

        $rideshare = [
            'cost' => $cost,
            'cost_raw' => $cost,
            'count' => 1,
            'country' => $country,
            'distance' => $oldTrip['distance'],
            'dup_id' => null,
            'duration' => $oldTrip['duration'],
            'id' => $oldTrip['id'],
            'location_end' => $destination['address'],
            'location_end_id' => $oldTrip['location_end_id'],
            'location_end_lat' => $destination['latitude'],
            'location_end_lng' => $destination['longitude'],
            'location_end_tz' => $destination['timezone'],
            'location_start' => $origin['address'],
            'location_start_id' => $oldTrip['location_start_id'],
            'location_start_lat' => $origin['latitude'],
            'location_start_lng' => $origin['longitude'],
            'location_start_tz' => $origin['timezone'],
            'member_id' => $member_id,
            'options' => [],
            'parsedemail_item_id' => null, /* $trip['id'] ??? */
            'private' => 't',
            'scheduled' => null,
            'trackedemail_item_id' => null,
            'transport_provider_id' => $transport_provider_id,
            'travel_date' => $oldTrip['travel_date'],
            'travel_date_end' => $oldTrip['travel_date_end'],
            'updated' => $oldTrip['updated']
        ];
        // Create trip advice
        $rideshare["source"] = strtolower($option["name"]);
        $parsedemail_item_advice_google_id = Options::saveTripAdvice($db, $rideshare);
        error_log(json_encode($parsedemail_item_advice_google_id));

        $leg_fare = $cost;
        // Get departure and arrival times...
        $date = new DateTime("now", new DateTimeZone($tz));
        $date->add(new DateInterval('PT15M'));
        $departure_time = $date->getTimestamp();
        $duration = Route::getDuration(array_key_exists('duration',$oldTrip)?$oldTrip['duration']:900);
        $date->add(new DateInterval('PT'.$duration.'M'));
        $arrival_time = $date->getTimestamp();

        $leg = [
            'id' => NULL,
            'parsedemail_item_advice_google_id' => $parsedemail_item_advice_google_id,
            'arrival_time' => $arrival_time,
            'arrival_time_zone' => $destination['timezone'],
            'arrival_timezone' => $tz,
            'departure_time' => $departure_time,
            'departure_time_zone' => $origin['timezone'],
            'departure_timezone' => $tz,
            'distance' => $oldTrip['distance'],
            'duration' => $oldTrip['duration'],
            'fare_raw' => $cost,
            'polyline' => $overview_polyline,
            'quote' => $cost,
            'steps' => count($leg_steps),
        ];
        // Create leg
        $google_directions_leg_id = Options::saveTripAdviceLeg($db, $rideshare, $leg);
        $leg['id'] = $google_directions_leg_id;
        foreach ($leg_steps as $i=>$step) {
            $step['google_directions_leg_id'] = $google_directions_leg_id;
            $google_directions_leg_step_id = Options::saveTripAdviceLegStep($db, $rideshare, $leg, $step);
            $step['id'] = $google_directions_leg_step_id;
            $leg_steps[$i] = $step;
        }
        $options = [
            'gid' => $google_directions_leg_id, /* ??? */
            'leg_fare' => [$google_directions_leg_id => $leg_fare],
            'leg_steps' => [$google_directions_leg_id => $leg_steps],
            'legs' => [$leg],
            'routes' => 1
        ];
        $rideshare['options'] = $options;
        return [$option, $rideshare, $route];
    }

    protected static function getDuration($duration=900) {
        if ($duration==NULL) {
            return 15;
        }
        if ($duration>60) {
            return (int)($duration/60);
        }
        return 1;
    }

    public static function processPolyline($data) {
        if (!is_array($data)) {
          return $data;
        }
        require_once('../common/Polyline.php');
        $points = [];
        foreach ($data as $item) {
            $points = array_merge($points, Polyline::decode($item));
            //$points = array_merge(Polyline::decode($item),$points);
            break;
        }
        return Polyline::encode($points);
    }

    public static function tripService($id, $country, $mode) {
        global $savvyext;
        $httpAuthToken = $savvyext->cfgReadChar('system.oauth2_token');
        $oauth2_url = $savvyext->cfgReadChar('system.oauth2_url');
        error_log("Route::tripService($id)");
        if ($country!='SG' && $country!='US') {
            throw new Exception('Unsupported country: '.$country);
        }
        // curl -d 'id=44254' -d 'mode=BART' -d 'country=US' -H 'Authorization: Server-Token 99dfe35fcb7de1ee' -X POST {$oauth2_url}modetrips
        $postdata = http_build_query(
            array(
                'id'=> $id,
                'mode' => $mode,
                'country' => $country
            )
        );
        $url = $oauth2_url."modetrips";
        $opts = array(
            'http' => array(
                'method' => "POST",
                'timeout' => 60, /* 1 minute */
                'header'  => 
                    "Content-Type: application/x-www-form-urlencoded\r\n" .
                    "Accept: application/json\r\n" .
                    "Authorization: Server-Token ${httpAuthToken}\r\n",
                'content' => $postdata
            ),
            "ssl" => array(
              "verify_peer"=>false,
              "verify_peer_name"=>false,
            )
        );
        $context = stream_context_create($opts);
        error_log("started trips service call");
        $t = time();
        $body = file_get_contents($url, false, $context);
        error_log("complete trips service call: ".(time()-$t)." seconds");
        $trip = json_decode($body,true);
        return $trip;
    }
}
/*
          ["id"]=>
          string(1) "5"
          ["city_id"]=>
          string(1) "2"
          ["name"]=>
          string(4) "Uber"
          ["transport_provider_id"]=>
          string(1) "1"
          ["custom"]=>
          NULL
*/

// vi:ts=2

