<?php

use GuzzleHttp\Client;
use GuzzleHttp\Psr7;
use GuzzleHttp\Exception\ClientException;
use GuzzleHttp\Exception\RequestException;

class Autocab {

	const TIMEOUT = 60;
	const DEBUG = false;
	const SSL_VERIFY = false;

	const REASON_NOT_REQUIRED = "NotRequired";
	const REASON_PRICE_CHANGED = "PriceChanged";

	private $httpClient;
	private $agentId;
	private $agentPassword;
	private $currency;
	private $vendorId;
	private $templatesDir;
	private $config = [];
	private $templates = [];


	public function __construct($config) {
		if (empty($config)) {
			throw new Exception("Configuration is not provided");
		}
		$this->setConfig($config);
		if (!isset($config["baseUrl"]) || empty($config["baseUrl"]))  {
			throw new Exception("The API URL is not specified");
		}

		$this->httpClient = new Client(['base_uri' => $config["baseUrl"]]);
	}

	public function setConfig($config) {
    global $savvyext;
    $this->config = $config;
		$this->agentId = $config["agentId"] ;
		$this->agentPassword = $config["agentPassword"];
		$this->currency = $config["currency"];
		$this->templatesDir = $config["templatesDir"];
		$this->loadTemplates($config);
	}

	public function loadTemplates($config) {
		if ( !array_key_exists('templatesDir', $config) || empty($config["templatesDir"]))  {
			throw new Exception("The templates dir is not specified");
		}
    $files = scandir($this->templatesDir);
		if ($files) {
      foreach ($files as $templateFile) {
        $template = file_get_contents($this->templatesDir."/".$templateFile);
        if ($template) {
          $this->templates[$templateFile] = $template;
        }
      }
    }
		if (count($this->templates) == 0) {
			throw new Exception("Any one template is not initialized");
		}
	}

	public function getTemplate($name, $oldRootTag=NULL, $newRootTag=NULL) {
	  if (!isset($this->templates[$name])) {
	    return false;
    }
    $template = $this->templates[$name];
	  if (isset($oldRootTag) && isset($newRootTag)) {
      $template = str_replace($oldRootTag, $newRootTag, $template);
    }
    $xmlElement = new SimpleXMLElement($template);
    $xmlElement->Agent->attributes()->Id = $this->config["agentId"];
    $xmlElement->Agent->Password = $this->config["agentPassword"];
	  return $xmlElement;
  }

	public function getHeaders($extraHeaders = []) {
		$headers = [
			'content-type' => 'text/xml',
		];
		return array_merge($headers,$extraHeaders);
	}

	public function getAvailability($source, $destination) {
		if (!isset($this->templates['autocab_agent_bid_request.xml'])) {
			throw new Exception("Bid request template is not initialized");
		}

		$uri = '/api/agent';
		$xmlElement = $this->getTemplate('autocab_agent_bid_request.xml');

		$xmlElement->Agent->Time                                         = gmdate("Y-m-d\TH:i:s.v\Z");
		$xmlElement->BidParameters->Journey->From->Data                  = $source["address"];
		$xmlElement->BidParameters->Journey->From->Coordinate->Latitude  = $source["latitude"];
		$xmlElement->BidParameters->Journey->From->Coordinate->Longitude = $source["longitude"];
		$xmlElement->BidParameters->Journey->To->Data                    = $destination["address"];
		$xmlElement->BidParameters->Journey->To->Coordinate->Latitude    = $destination["latitude"];
		$xmlElement->BidParameters->Journey->To->Coordinate->Longitude   = $destination["longitude"];

		$xml = $xmlElement->asXML();

		$result = $this->genericPost($uri, $xml);

		if ($result["error"]) {
			return $result;
		}

		if ($result["code"] != 200) {
			var_dump($result["data"]);
		}

		$xmlResponse = new SimpleXMLElement($result["data"]);
		$error = self::processError($xmlResponse);
		if($error) {
			return ["error"=>1, "data" => $error, "request"=>$xmlElement];
		}
		$costs = [];
		foreach ($xmlResponse->Offers->Offer as $offer) {
			$item = self::processBidOffer($offer);
			array_push($costs, $item);
		}
		return array("error"=>0, "data"=>$costs, "raw_data"=>$xmlResponse, "request"=>$xmlElement);
	}

	public function getBookingAvailability($source, $destination, $vendor) {

		if (!isset($this->templates['autocab_agent_booking_availability_request.xml'])) {
			throw new Exception("Booking Availability request template is not initialized");
		}

		$uri = '/api/agent';
		$xmlElement = $this->getTemplate('autocab_agent_booking_availability_request.xml');
		$xmlElement->Vendor->attributes()->Id                                = $vendor;
		$xmlElement->Agent->Time                                             = gmdate("Y-m-d\TH:i:s.v\Z");
		$xmlElement->BookingParameters->Journey->From->Data                  = $source["address"] ?? "No Address";
		$xmlElement->BookingParameters->Journey->From->Coordinate->Latitude  = $source["latitude"];
		$xmlElement->BookingParameters->Journey->From->Coordinate->Longitude = $source["longitude"];
		$xmlElement->BookingParameters->Journey->To->Data                    = $destination["address"] ?? "No Address";
		$xmlElement->BookingParameters->Journey->To->Coordinate->Latitude    = $destination["latitude"];
		$xmlElement->BookingParameters->Journey->To->Coordinate->Longitude   = $destination["longitude"];

		$xml = $xmlElement->asXML();

		$result = $this->genericPost($uri, $xml);

		if ($result["error"]) {
			return $result;
		}

		if ($result["code"] != 200) {
			var_dump($result["data"]);
		}

		$xmlResponse = new SimpleXMLElement($result["data"]);
		$error = self::processError($xmlResponse);
		if($error) {
			return ["error"=>1, "data" => $error, "request"=>$xmlElement];
		}

		$cost = null;

		if(isset($xmlResponse->Pricing)) {
			$cost = [
				'currency' => (string)$xmlResponse->Pricing->Currency,
				'availability_ref' => (string)$xmlResponse->AvailabilityReference,
				'display_name' => (string)$xmlResponse->VendorDetails->Name,
				'high_estimate' => (int)$xmlResponse->Pricing->Price,
				'low_estimate' => (int)$xmlResponse->Pricing->Price,
				'distance_estimate' => (int) $xmlResponse->EstimatedJourney->Distance,
				'duration_estimate' => (int) $xmlResponse->EstimatedJourney->Duration * 60,
        'vendor_id' => (string) $xmlResponse->Vendor->attributes()->Id,
			];
		}

		return ["error"=>0, "data"=>$cost, "raw_data"=>$xmlResponse, "request"=>$xmlElement];
	}

	public function cancelAvailability($availabilityRef, $vendor, $reason = self::REASON_NOT_REQUIRED) {

		if (!isset($this->templates['autocab_agent_booking_not_authorized_request.xml'])) {
			throw new Exception("BookingNotAuthorizedRequest template is not initialized");
		}

		if (!isset($availabilityRef)) {
			throw new Exception("Invalid availability reference");
		}

		$uri = '/api/agent';
		$xmlElement = $this->getTemplate('autocab_agent_booking_not_authorized_request.xml');
		$xmlElement->Vendor->attributes()->Id = $vendor;
		$xmlElement->Agent->Time              = gmdate("Y-m-d\TH:i:s.v\Z");
		$xmlElement->AvailabilityReference    = $availabilityRef;
		$xmlElement->Reason                   = $reason;

		$xml = $xmlElement->asXML();

		$result = $this->genericPost($uri, $xml);

		if ($result["error"]) {
			return $result;
		}

		if ($result["code"] != 200) {
			var_dump($result["data"]);
		}

		$xmlResponse = new SimpleXMLElement($result["data"]);
		$error = self::processError($xmlResponse);
		if($error) {
			return ["error"=>1, "data" => $error, "request"=>$xmlElement];
		}
		return ["error"=>0, "data"=>[], "raw_data"=>$xmlResponse, "request"=>$xmlElement];
	}

	public function booking($availabilityRef, $passengers=[], $vendor) {

		if (!isset($this->templates['autocab_agent_booking_athorization_request.xml'])) {
			throw new Exception("BookingAuthorization template is not initialized");
		}

		if (!isset($availabilityRef)) {
			throw new Exception("Invalid availability reference");
		}

		if (!is_array($passengers) || count($passengers) < 1
		    || !array_key_exists("name", $passengers[0]) || empty($passengers[0]["name"])
		) {
			throw new Exception("Invalid passengers information");
		}

		$uri = '/api/agent';
		$xmlElement = $this->getTemplate('autocab_agent_booking_athorization_request.xml');
		$xmlElement->Vendor->attributes()->Id = $vendor;
		$xmlElement->Agent->Time              = gmdate("Y-m-d\TH:i:s.v\Z");
		$xmlElement->AvailabilityReference    = $availabilityRef;
		foreach ($passengers as $key => $passenger) {
			$xmlPassenger = $xmlElement->Passengers->addChild("PassengerDetails");
			$isLead = ($key === array_keys($passengers)[0]) ? "true" : "false";
			$xmlPassenger->addAttribute("IsLead", "$isLead");
			$xmlPassenger->addChild("Name", $passenger["name"]);
			if (array_key_exists("phone", $passenger) && isset($passenger["phone"])) {
				$xmlPassenger->addChild( "TelephoneNumber", $passenger["phone"] );
			}
			if (array_key_exists("email", $passenger) && isset($passenger["email"])) {
				$xmlPassenger->addChild("EmailAddress", $passenger["email"]);
			}
		}
		$xml = $xmlElement->asXML();

		$result = $this->genericPost($uri, $xml);

		if ($result["error"]) {
			return $result;
		}

		if ($result["code"] != 200) {
			var_dump($result["data"]);
		}

		$xmlResponse = new SimpleXMLElement($result["data"]);
		$error = self::processError($xmlResponse);
		if($error) {
			return ["error"=>1, "data" => $error];
		}

		$resp = [];
		if(isset($xmlResponse->AuthorizationReference)) {
			$resp = [
				'authorization_ref' => (string)$xmlResponse->AuthorizationReference,
				'booking_ref' => (string)$xmlResponse->BookingReference,
			];
		}

		return ["error"=>0, "data"=>$resp, "raw_data"=>$xmlResponse, "request"=>$xmlElement];
	}

	public function bookingStatus($authorizationRef, $vendor) {

		if (!isset($this->templates['autocab_agent_booking_status_request.xml'])) {
			throw new Exception("BookingStatusRequest template is not initialized");
		}

		if (!isset($authorizationRef)) {
			throw new Exception("Invalid authorization reference");
		}

		$uri = '/api/agent';
		$xmlElement = $this->getTemplate('autocab_agent_booking_status_request.xml');
		$xmlElement->Vendor->attributes()->Id = $vendor;
		$xmlElement->Agent->Time              = gmdate("Y-m-d\TH:i:s.v\Z");
		$xmlElement->AuthorizationReference    = $authorizationRef;

		$xml = $xmlElement->asXML();

		$result = $this->genericPost($uri, $xml);

		if ($result["error"]) {
			return $result;
		}

		if ($result["code"] != 200) {
			var_dump($result["data"]);
		}

		$xmlResponse = new SimpleXMLElement($result["data"]);
		$error = self::processError($xmlResponse);
		if($error) {
			return ["error"=>1, "data" => $error, "request"=>$xmlElement];
		}

		$resp = [
			'booking_ref' => (string)$xmlResponse->BookingReference,
			'status' => (string)$xmlResponse->Status,
			'cancellation_reason' => (string)$xmlResponse->CancellationReason
		];
		return ["error"=>0, "data"=>$resp, "raw_data"=>$xmlResponse, "request"=>$xmlElement];
	}

	public function bookingCancellation($authorizationRef, $vendor) {

		if (!isset($this->templates['autocab_agent_booking_cancellation_request.xml'])) {
			throw new Exception("BookingCancellationRequest template is not initialized");
		}

		if (!isset($authorizationRef)) {
			throw new Exception("Invalid authorization reference");
		}

		$uri = '/api/agent';
		$xmlElement = $this->getTemplate('autocab_agent_booking_cancellation_request.xml');
		$xmlElement->Vendor->attributes()->Id = $vendor;
		$xmlElement->Agent->Time              = gmdate("Y-m-d\TH:i:s.v\Z");
		$xmlElement->AuthorizationReference    = $authorizationRef;

		$xml = $xmlElement->asXML();

		$result = $this->genericPost($uri, $xml);

		if ($result["error"]) {
			return $result;
		}

		if ($result["code"] != 200) {
			var_dump($result["data"]);
		}

		$xmlResponse = new SimpleXMLElement($result["data"]);
		$error = self::processError($xmlResponse);
		if($error) {
			return ["error"=>1, "data" => $error, "code" => 500, "request"=>$xmlElement];
		}

		$resp = [];
		return ["error"=>0, "data"=>$resp, "code" => 200, "raw_data"=>$xmlResponse, "request"=>$xmlElement];
	}


	private function genericPost($uri, $xml, $extraHeaders=[]) {
		$code = 0;
		$data = [];
		$error = 0;
		try {
			$json = json_encode($data);
			$length = strlen($json);
			$response = $this->httpClient->request('POST', $uri, [
				'decode_content' => true,
				'connect_timeout' => SELF::TIMEOUT,
				'debug' => SELF::DEBUG,
				'verify' => SELF::SSL_VERIFY,
				'headers' => $this->getHeaders(),
				'body' => $xml
			]);
			$code = $response->getStatusCode();
			$data = $response->getBody();
		} catch (RequestException $e) {
			if (SELF::DEBUG) {
				echo Psr7\str($e->getRequest());
				if ($e->hasResponse()) {
					echo Psr7\str($e->getResponse());
				}
			}
			if ($e->hasResponse()) {
				$code = (int) $e->getResponse()->getStatusCode();
			}
			$data = ["error" => "exception", "error_description" => $e->getMessage()];
			$error = 1;
		} catch (ClientException $e) {
			if (SELF::DEBUG) {
				echo Psr7\str($e->getRequest());
				echo Psr7\str($e->getResponse());
			}
			$code = (int)$e->getResponse()->getStatusCode();
			$data = ["error" => "exception", "error_description" => $e->getMessage()];
			$error = 1;
		} catch (Exception $e) {
			if (SELF::DEBUG) {
				echo Psr7\str($e->getRequest());
				echo Psr7\str($e->getResponse());
			}
			$code = (int)$e->getResponse()->getStatusCode();
			$data = ["error" => "exception", "error_description" => $e->getMessage()];
			$error = 1;
		}
		return ["error" => $error, "data" => $data, "code" => $code];
	}

	public static function processError($xmlResponse) {
		if ((string)$xmlResponse->Result->Success == "true") {
			return false;
		}
		return ["error"=>"API error", "error_description" => (string) $xmlResponse->Result->FailureReason, "error_code" => (string) $xmlResponse->Result->FailureCode];
	}

	public static function processBidOffer($offer) {
		$item = [
			'currency' => (string)$offer->Pricing->Currency,
			'transport_product' => (string)$offer['Reference'],
			'display_name' => (string)$offer->VendorDetails->Name,
			'high_estimate' => (int)$offer->Pricing->Price,
			'low_estimate' => (int)$offer->Pricing->Price,
			'distance_estimate' => (int) $offer->EstimatedJourney->Distance,
			'duration_estimate' => (int) $offer->EstimatedJourney->Duration * 60,
		];
		return $item;
	}

}