<?php

namespace Drupal\kate_demo\Controller;

use Drupal\Core\Controller\ControllerBase;

class MyController extends ControllerBase {

	// ******************************* GOOGLE SPREADSHEETS CODE *******************************
	public function spreadsheet_export() {
		// Initiate OAuth2 authentication to request access to output a spreadsheet to the user's Google Drive.
		// Once the user approves this will hit us back at the oauth2_redirect_uri specified.
		$permissions = array( 'https://www.googleapis.com/auth/drive.file' );

		$url = \Drupal\Core\Url::fromUri( 'https://accounts.google.com/o/oauth2/auth', array( 'query' => array(
			'scope' => join( ' ', $permissions ),
			'redirect_uri' => \Drupal::config( 'kate_demo.settings' )->get( 'oauth2_redirect_uri' ),
			'response_type' => 'code',
			'client_id' => \Drupal::config( 'kate_demo.settings' )->get( 'google_oauth2_client_id' ),
			'approval_prompt' => 'auto',
		) ) );

		return new \Drupal\Core\Routing\TrustedRedirectResponse( $url->toString() );
	}

	public function oauth2_callback() {
		// user has granted us access to write a spreadsheet to their Google Drive
		if ( !empty( $_GET[ 'error' ] ) ) {
			\Drupal::messenger()->addError( t( 'Error acquiring Google API access.' ) );
			return $this->redirect( 'kate_demo.get_zipcode' );
		}
		else if ( !empty( $_GET[ 'code' ] ) ) {
			$oauth2_authorization_code = $_GET[ 'code' ];

			// exchange authorization code for an access token!  This apparently needs to be a POST
			// so I'm going to use the PHP curl extension for it.  May also work by building an
			// auto submitting form with all invisible fields, but then Google would see the POST coming
			// from the user's IP of course...

			$postfields = array(
				'code' => $oauth2_authorization_code,
				'client_id' => \Drupal::config( 'kate_demo.settings' )->get( 'google_oauth2_client_id' ),
				'client_secret' => \Drupal::config( 'kate_demo.settings' )->get( 'google_oauth2_client_secret' ),
				'redirect_uri' => \Drupal::config( 'kate_demo.settings' )->get( 'oauth2_redirect_uri' ),
				'grant_type' => 'authorization_code',
			);

			$ch = curl_init();
			curl_setopt( $ch, CURLOPT_URL, "https://www.googleapis.com/oauth2/v3/token" );
			curl_setopt( $ch, CURLOPT_RETURNTRANSFER, true );
			curl_setopt( $ch, CURLOPT_POST, true );
			// passing a pre-built query string makes curl encode the POST as application/x-www-form-urlencoded,
			// which Google apparently requires.  Not mentioned clearly in their API documentation!
			curl_setopt( $ch, CURLOPT_POSTFIELDS, http_build_query( $postfields ) );
			$response = curl_exec( $ch );
			curl_close( $ch );

			$data = json_decode( $response );
			if ( empty( $data->access_token ) ) {
				\Drupal::messenger()->addError( t( 'Error acquiring Google API access token.' ) );
				return $this->redirect( 'kate_demo.get_zipcode' );
			}

			$oauth2_access_token = $data->access_token;

			/*
            $session = \Drupal::service('session'); $session->start();
            $session->set( 'oauth2_access_token', $oauth2_access_token );
            $session->save();
            */

			return $this->redirect( 'kate_demo.spreadsheet_export_main', array( 'access_token' => $oauth2_access_token ) );
		}
	}

	public function spreadsheet_export_main( $access_token ) {
		// upload a TSV and tell Google to convert to a Spreadsheet
		// Tried CSV first and API complained.

		// parse our cookie into tab separated value content, MIME type text/tab-separated-values
        $session = \Drupal::service('tempstore.private')->get( 'my_session' );
        $data = $session->get( 'kate_demo_data' )[ 'days' ];
        
		$mypost = '';
		foreach ( $data as $day )
			$mypost .= join( "\t", $day ) . "\n";

		$mymetadata = array(
			'title' => 'Kate Demo Spreadsheet Export ' . date( 'Y-m-d' )
		);

		$separator = '--foo_bar_baz';
		$mymultipartpost = $separator . "\n" . "Content-Type: application/json; charset=UTF-8\n\n"
			. json_encode( $mymetadata )
			. "\n\n" . $separator . "\nContent-Type: text/tab-separated-values\n\n"
			. $mypost
			. "\n\n" . $separator . "--";

		// actually POST our upload to Google
		$ch = curl_init();
		curl_setopt( $ch, CURLOPT_URL, 'https://www.googleapis.com/upload/drive/v2/files?uploadType=multipart&convert=true' );
		curl_setopt( $ch, CURLOPT_RETURNTRANSFER, true );
		curl_setopt( $ch, CURLOPT_POST, true );
		curl_setopt( $ch, CURLOPT_POSTFIELDS, $mymultipartpost );
		curl_setopt( $ch, CURLOPT_HTTPHEADER, array(
			'Authorization: Bearer ' . $access_token,
			'Content-Type: multipart/related; boundary="foo_bar_baz"',
			'Content-Length: ' . strlen( $mymultipartpost )
		) );
		$response = curl_exec( $ch );
		curl_close( $ch );

		// fetch the link for the newly created spreadsheet out of Google's response and send the user there.
		$response_data = json_decode( $response );
		return new \Drupal\Core\Routing\TrustedRedirectResponse( $response_data->alternateLink );
	}
	// ******************************* END GOOGLE SPREADSHEETS CODE *******************************

	// ******************************* FACEBOOK CODE *******************************
	private function facebook_integration( &$attached ) {
        $session = \Drupal::service('tempstore.private')->get( 'my_session' );

        $attached[ 'drupalSettings' ][ 'kate_facebook_data' ] = $session->get( 'kate_demo_data' );
		$attached[ 'drupalSettings' ][ 'kate_demo_facebook_app_id' ] = \Drupal::config( 'kate_demo.settings' )->get( 'facebook_app_id' );
		$attached[ 'library' ][] = 'kate_demo/facebook';

		$description_parts = [];
		foreach( $attached[ 'drupalSettings' ][ 'kate_facebook_data' ][ 'days' ] as $day ) {
		    $description_parts[] = $day[ 0 ] . ': ' . $day[ 1 ] . 'F / ' . $day[ 2 ] . 'F';
        }

		$attached[ 'html_head' ] = [
            [
                [
                    '#tag' => 'meta',
                    '#attributes' => [
                        'name' => 'og:image',
                        'description' => '/sites/default/files/Meta%20Icon.jpg',
                    ],
                ],
                'Image'
            ],
            [
                [
                    '#tag' => 'meta',
                    '#attributes' => [
                        'name' => 'og:description',
                        'description' => join( '; ', $description_parts ),
                    ],
                ],
                'Description'
            ],
        ];

		return '<a id=\'facebook-share-link\' href=\'#\'\'>' . t( 'Click here to share this data on Facebook!' ) . '</a>';
	}
	// ******************************* END FACEBOOK CODE *******************************

	// ******************************* DYNAMICALLY CREATED DOWNLOAD OF THIS DEMO *******************************
	public function download_demo_code() {
		$fn = '/tmp/kate-demo-download-' . getmypid() . '-' . date( 'YmdHis' ) . '.tar.bz2';

		$cmd = "tar cjf ${fn} --directory="
			. dirname( $_SERVER[ 'DOCUMENT_ROOT' ] . '/' . drupal_get_path( 'module', 'kate_demo' ) )
			. ' kate_demo';

		system( $cmd );

		$raw = file_get_contents( $fn );
		unlink( $fn );

		Header( 'Content-Type: application/x-tar' );
		Header( 'Content-Disposition: inline; filename="' . basename( $fn ) . '"' );
		echo $raw;
		ob_flush();
		sleep( 1 );
		die();
	}
	// ******************************* END DOWNLOAD CODE *******************************

	// ******************************* MAPQUEST CODE *******************************
	private function mapquest_map( $data_location, &$attached ) {
		$attached[ 'library' ][] = 'kate_demo/mapquest';
		$attached[ 'library' ][] = 'kate_demo/main';
		$attached[ 'drupalSettings' ][ 'mapquest_maps_data' ] = $data_location;

		return '<div id=\'map-canvas\'></div>';
	}
	// ******************************* END MAPQUEST CODE *******************************

    // ********************* OPENSTREETMAPS LEAFLET CODE *******************************
    private function openstreetmaps_leaflet_map( $data_location, &$attached ) {
        $attached[ 'library' ][] = 'kate_demo/openstreetmaps_leaflet';
        $attached[ 'library' ][] = 'kate_demo/main';
        $attached[ 'drupalSettings' ][ 'openstreetmaps_maps_data' ] = $data_location;

        return '<div id=\'map-canvas\'></div>';
    }
    // ******************************* END MAPQUEST CODE *******************************

	// ******************************* SVG GRAPHING CODE *******************************
	private function get_svg_line( $DOM, $x1, $y1, $x2, $y2, $stroke = 'black' ) {
		$line = $DOM->createElement( 'line' );
		$parms = compact( 'x1', 'y1', 'x2', 'y2', 'stroke' );
		foreach ( array( 'x1', 'y1', 'x2', 'y2', 'stroke' ) as $fld )
			$line->setAttribute( $fld, $parms[ $fld ] );
		return $line;
	}

	private function get_svg_text( $DOM, $x, $y, $myText, $textLength = null, $fill = 'black', $font_size = 12 ) {
		$text = $DOM->createElement( 'text' );
		$lengthAdjust = 'spacingAndGlyphs';
		$parms = compact( 'x', 'y', 'fill', 'textLength', 'lengthAdjust' );
		$parms[ 'font-size' ] = $font_size;   // PHP doesn't allow dashes in variable names, hence this hack
		foreach ( array( 'x', 'y', 'fill', 'textLength', 'font-size', 'lengthAdjust' ) as $fld )
			if ( $parms[ $fld ] != null )
				$text->setAttribute( $fld, $parms[ $fld ] );
		$text->appendChild( $DOM->createTextNode( $myText ) );
		return $text;
	}

	private function get_svg_circle( $DOM, $cx, $cy, $r, $stroke = 'black', $fill = 'black' ) {
		$circle = $DOM->createElement( 'circle' );
		$parms = compact( 'cx', 'cy', 'r', 'stroke', 'fill' );
		foreach ( array( 'cx', 'cy', 'r', 'stroke', 'fill' ) as $fld )
			$circle->setAttribute( $fld, $parms[ $fld ] );
		return $circle;
	}

	private function get_y_for_temp( $height, $Margin, $highest, $lowest, $thisTemp ) {
		$pixel_range = $height - $Margin;
		$temp_range = $highest - $lowest;

		$scaled_temp = floor( (($thisTemp - $lowest) * $pixel_range) / $temp_range );
		return $height - $Margin - $scaled_temp;
	}
	
	public function graph_weather( $width, $height ) {
		// main entry point for creating SVG graph of weather forecast.  This is linked to from the Drupal menu hook.
        $session = \Drupal::service('tempstore.private')->get( 'my_session' );
        $data = $session->get( 'kate_demo_data' )[ 'days' ];

		// derive some additional numbers to be used in graphing
		$Margin = 15;
		$num_points = count( $data );
		$highest = -999;
		$lowest = 999;
		foreach ( $data as $point ) {
            $high = floatval( $point[ 'high' ] );
            $low = floatval( $point[ 'low' ] );
			if ( $high > $highest ) $highest = $high;
			if ( $low < $lowest ) $lowest = $low;
		}
		$point_interval = floor( ($width - $Margin) / $num_points );
		// add some space at the top and bottom of the graph so our dots don't hit the axis
		$temp_range = $highest - $lowest;
		$highest += floor( $temp_range / 10 );
		$lowest -= floor( $temp_range / 10 );

		// this function outputs SVG code for the graph
		$DOM = new \DOMdocument;
		$SVG = $DOM->createElement( 'svg' );
		$SVG->setAttribute( 'xmlns', 'http://www.w3.org/2000/svg' );
		$DOM->appendChild( $SVG );
		$SVG->setAttribute( 'height', $height );
		$SVG->setAttribute( 'width', $width );

		// draw our axis
		$SVG->appendChild( $this->get_svg_line( $DOM, $Margin, 0, $Margin, $height - $Margin ) );
		$SVG->appendChild( $this->get_svg_line( $DOM, $Margin, $height - $Margin, $width, $height - $Margin ) );

		// draw data points
		$i = 0;
		$last_high = null;
		$last_low = null;
		foreach ( $data as $point ) {
            $label = $point[ 'label' ];
            $high = floatval( $point[ 'high' ] );
            $low = floatval( $point[ 'low' ] );


			// x is used for high, for low and for labels
			$x = $Margin + ($i * $point_interval) + floor( $point_interval / 2 );

			// axis label
			$SVG->appendChild( $this->get_svg_line( $DOM, $x, $height - $Margin, $x, $height - $Margin + 3 ) );
			$SVG->appendChild( $this->get_svg_text( $DOM, $x - floor( $point_interval / 2 ) + $Margin, $height - 3, $label, $point_interval - ($Margin * 2) ) );

			// actual temps
			$y_high = $this->get_y_for_temp( $height, $Margin, $highest, $lowest, $high );
			$y_low = $this->get_y_for_temp( $height, $Margin, $highest, $lowest, $low );
			$SVG->appendChild( $this->get_svg_circle( $DOM, $x, $y_high, 3, 'green', 'green' ) );
			$SVG->appendChild( $this->get_svg_circle( $DOM, $x, $y_low, 3, 'red', 'red' ) );
			// temp text labels
			$SVG->appendChild( $this->get_svg_text( $DOM, $x - 8, $y_high - 5, $high, 16 ) );
			$SVG->appendChild( $this->get_svg_text( $DOM, $x - 8, $y_low - 5, $low, 16 ) );

			// lines from previous point
			if ( !empty( $last_high ) ) {
				$SVG->appendChild( $this->get_svg_line( $DOM, $last_high[ 'x' ], $last_high[ 'y' ], $x, $y_high, 'green' ) );
				$SVG->appendChild( $this->get_svg_line( $DOM, $last_low[ 'x' ], $last_low[ 'y' ], $x, $y_low, 'red' ) );
			}

			// save this column's points so we can draw lines to them from the next point
			$last_high = array( 'x' => $x, 'y' => $y_high );
			$last_low = array( 'x' => $x, 'y' => $y_low );

			$i++;
		}

		// label y axis
		$interval = floor( $temp_range / 5 );
		for ( $temp = $lowest + $interval; $temp < $highest - $interval; $temp += $interval ) {
			$y = $this->get_y_for_temp( $height, $Margin, $highest, $lowest, $temp );
			$SVG->appendChild( $this->get_svg_text( $DOM, 0, $y + 5, $temp, $Margin - 4 ) );
			$SVG->appendChild( $this->get_svg_line( $DOM, $Margin - 3, $y, $Margin, $y ) );
		}

		$DOM->formatOutput = true;

		// die instead of return so we don't get the Drupal headers on top of our code
		Header( 'Content-Type: image/svg+xml' );
        header("Expires: Tue, 03 Jul 2001 06:00:00 GMT");
        header("Last-Modified: " . gmdate("D, d M Y H:i:s") . " GMT");
        header("Cache-Control: no-store, no-cache, must-revalidate, max-age=0");
        header("Cache-Control: post-check=0, pre-check=0", false);
        header("Pragma: no-cache");
        header("Connection: close");
        
		die( $DOM->saveXML( $SVG ) );
	}
	// ******************************* END SVG GRAPHING CODE *******************************

	// ******************************* EBAY CODE *******************************
	private function return_ebay_credentials()
	{
		$devid	=	\Drupal::config('kate_demo.settings')->get('ebay_devid');
		$appid	=	\Drupal::config('kate_demo.settings')->get('ebay_appid');
		$certid	=	\Drupal::config('kate_demo.settings')->get('ebay_certid');
		$token	=	\Drupal::config('kate_demo.settings')->get('ebay_token');

		return compact( 'devid', 'appid', 'certid', 'token' );
	}

	private function ebay_access_callback()
	{
		$cred	=	$this->return_ebay_credentials();

		return ( !empty( $cred[ 'devid' ] ) && !empty( $cred[ 'appid' ] ) && !empty( $cred[ 'certid' ] ) && !empty( $cred[ 'token' ] ) );
	}

    private function do_ebay_browse_api_search( $token, $zipcode, $keywords, $num_results ) {
        $endpoint = 'https://api.ebay.com/buy/browse/v1/item_summary/search';
        $query = http_build_query([
            'q' => $keywords,
            'filter' => 'deliveryPostalCode:' . $zipcode . ',deliveryCountry:US',

            'sort' => 'distance',
            'limit' => $num_results
        ]);

        $url = $endpoint . '?' . $query;

        echo $url;

        $headers = [
            "Authorization: Bearer $token",
            "Content-Type: application/json",
            "Accept: application/json"
        ];

        $ch = curl_init();
        curl_setopt($ch, CURLOPT_URL, $url);
        curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);

        $response = curl_exec($ch);
        if ($response === false) {
            die('Curl error: ' . curl_error($ch));
        }
        curl_close($ch);

        $data = json_decode($response, true);

        return $data;
    }

	private function get_ebay_items( $zipcode, $keywords = 'weather', $num_results = 20 )
	{
		//include_once( $_SERVER[ 'DOCUMENT_ROOT' ] . '/' . drupal_get_path( 'module', 'kate_demo' ) . '/eBayClass.php' );
		$cred		=	$this->return_ebay_credentials();
		//$myeBay		=	new \eBayClass( $cred[ 'devid' ], $cred[ 'appid' ], $cred[ 'certid' ], $cred[ 'token' ] );

        $response = $this->do_ebay_browse_api_search( $cred[ 'token' ], $zipcode, $keywords, $num_results );

		$items		=	$response[ 'itemSummaries' ];
		$rows		=	array();
		foreach ( $items as $item )
		{
			$row	=	array();

            $itemWebUrl = $item[ 'itemWebUrl' ];
            $galleryURL = $item[ 'image' ][ 'imageUrl' ];

			$row[ 'itemId' ] = \Drupal\Core\Link::fromTextAndUrl( $item[ 'itemId' ],
				\Drupal\Core\Url::fromUri( $itemWebUrl, [ 'attributes' => [ 'target' => '_blank' ] ] )
				)->toString();

			$row[ 'title' ] = \Drupal\Core\Link::fromTextAndUrl( $item[ 'title' ],
				\Drupal\Core\Url::fromUri( $itemWebUrl, [ 'attributes' => [ 'target' => '_blank' ] ] )
				)->toString();

			$row[ 'galleryURL' ] = \Drupal\Core\Link::fromTextAndUrl( [ '#markup' => '<img src="' . $galleryURL . '" />' ],
				\Drupal\Core\Url::fromUri( $itemWebUrl, [ 'attributes' => [ 'target' => '_blank' ] ] )
			)->toString();

            $row[ 'location' ] = join( ' ', array_values( $item[ 'itemLocation' ] ) );

			$rows[]		=	$row;
		}

		if ( empty( $rows ) )
			echo $items->saveXML();

		$header		=	array( t( 'Item ID' ), t( 'Title' ), t( 'Photo' ), t( 'Where' ) );

		$eBayItemsTableArray = [
			'#type' => 'table',
			'#header' => $header,
			'#rows' => $rows
		];

		return $eBayItemsTableArray;
	}

	public function ebay_form_submit()
	{
		die( \Drupal::service("renderer")->render( $this->get_ebay_items( $_POST[ 'zipcode_h' ], $_POST[ 'keywords' ], $_POST[ 'num_results' ] ) ) );
	}

	public function get_ebay_form( $zipcode )
	{
		// generates an ebay search form using the drupal forms system and outputs it for 
		// use inside a jqueryUI dialog
		$form	=	\Drupal::formBuilder()->getForm( '\Drupal\kate_demo\Form\eBayForm', $zipcode );
		die( \Drupal::service("renderer")->render( $form ) );
	}

	private function get_ebay_link( $zipcode, & $attached )
	{
		// calls in jQuery UI dialog and also ebay.js
		$attached[ 'library' ][] = 'kate_demo/ebay';
		$attached[ 'library' ][] = 'kate_demo/main';
		$attached[ 'drupalSettings' ][ 'loader_image_url' ] = drupal_get_path( 'module', 'kate_demo' ) . '/images/ajax_loader_green_512.gif';

		$return = \Drupal\Core\Link::createFromRoute( 'Click here for some weather related eBay items on sale near this zipcode!',
			'kate_demo.get_ebay_form', [ 'zipcode' => $zipcode ], [ 'attributes' => [ 'target' => '_blank', 'id' => 'ebay-form-link' ] ] )->toString();

		return $return;
	}
	// ******************************************** END EBAY CODE ****************************

	// ******************************* DOWNLOAD AS PDF CODE *******************************
	private function convert_relative_to_absolute_single_url( $url )
	{
		// this misses a lot of edge cases but should work fine for my purpose
		if ( substr( $url, 0, 4 ) != 'http' )
		{
			if ( substr( $url, 0, 1 ) != '/' )
				$url 		=	'/' . $url;

			$url	=	'http://' . $_SERVER[ 'HTTP_HOST' ] . $url;
		}

		return $url;
	}

	private function convert_relative_to_absolute_urls( $content )
	{
		// so the images and links on my cover letter page don't end up broken
		$DOM	=	new \DOMdocument;
		@$DOM->loadHTML( $content );

		$links	=	$DOM->getElementsByTagName( 'a' );
		foreach ( $links as $link )
			$link->setAttribute( 'href', $this->convert_relative_to_absolute_single_url( $link->getAttribute( 'href' ) ) );

		$imgs	=	$DOM->getElementsByTagName( 'img' );
		foreach ( $imgs as $img )
			$img->setAttribute( 'src', $this->convert_relative_to_absolute_single_url( $img->getAttribute( 'src' ) ) );

		return $DOM->saveHTML();
	}

	public function download_node_as_pdf( $nid )
	{
		$node		=	\Drupal::entityTypeManager()->getStorage('node')->load($nid);
		$content	=	$this->convert_relative_to_absolute_urls( $node->get( 'body' )->getValue()[0]['value'] );

		// output this node's html content into a temporary text file
		$fn		=	'/tmp/kate-demo-pdf-' . getmypid() . '-' . date( 'YmdHis' );
		$f		=	fopen( $fn . '.html', 'w' );
		fwrite( $f, $content );
		fclose( $f );

		// run text file through wkhtmltopdf to generate pdf
		//$cmd	=	variable_get( 'kate_demo_wkhtmltopdf_binary' ) . ' ' . $fn . '.html ' . $fn . '.pdf';
		$cmd   =   "node " . $_SERVER[ 'DOCUMENT_ROOT' ] . '/' . drupal_get_path( 'module', 'kate_demo' ) .
			'/node/mkpdf.js ' . $fn . '.html ' . $fn . '.pdf';
		//echo $cmd . "\n"; die();
		//$cmd .= " >> /tmp/my.log 2>&1";
		system( $cmd . ' &' );

		// remove the html
		unlink( $fn . '.html' );

		// load our pdf data then delete our pdf
		$pdf_content	=	file_get_contents( $fn . '.pdf' );
		unlink( $fn . '.pdf' );

		// send PDF to user
		Header( 'Content-Type: application/pdf' );
		Header( 'Content-Disposition: inline; filename="' . basename( $fn ) . '.pdf"' );
		echo $pdf_content;
		ob_flush();
		sleep( 1 );
		die();
	}
	// ******************************* END PDF CODE *******************************

	private function fetch_url( $url ) {
		$ch = curl_init();
		curl_setopt( $ch, CURLOPT_URL, $url );
		curl_setopt( $ch, CURLOPT_RETURNTRANSFER, true );
		curl_setopt( $ch, CURLOPT_HTTPHEADER, array(
			'User-Agent: Kate\'s Demo/1 When you\'re Strange...' ) );
		$ret = curl_exec( $ch );
		curl_close( $ch );
		return $ret;
	}

    private function getDOMFromURL( $url ) {
        $details = parse_url( $url );
        $referer = $details[ 'scheme' ] . '://' . $details[ 'host' ];

        $ch = curl_init( $url );
        curl_setopt( $ch, CURLOPT_USERAGENT, file_get_contents( '/var/www/html/userAgent-string.txt' ) );
        curl_setopt( $ch, CURLOPT_REFERER, $referer );
        curl_setopt( $ch, CURLOPT_RETURNTRANSFER, true );
        curl_setopt( $ch, CURLOPT_VERBOSE, false );
        curl_setopt( $ch, CURLOPT_FOLLOWLOCATION, true );
        $raw = curl_exec( $ch );
        curl_close( $ch );

        $DOM = new \DOMdocument;
        @$DOM->loadHTML( $raw );
        return $DOM;
    }

	private function geocode( $zipcode ) {
		$url_template = 'https://nominatim.openstreetmap.org/search.php?q=[ZIPCODE]&polygon_geojson=1&format=jsonv2';
		$url = str_replace( array( '[ZIPCODE]' ), array( $zipcode ), $url_template );
		$raw = $this->fetch_url( $url );
		$json = json_decode( $raw );

		if ( $json ) {
			return array(
				'lat' => floatval( $json[ 0 ]->lat ),
				'lon' => floatval( $json[ 0 ]->lon ),
				'display_name' => $json[ 0 ]->display_name,
				'zip' => $zipcode,
			);
		} else
			return null;
	}

    private function getNodesWithAttribute( $DOM, $nodeName, $attributeName, $attributeValue, $contextNode = null ) {
        $xpath = new \DOMXPath( $DOM );
        $q = './/' . $nodeName . '[@' . $attributeName . '=\'' . $attributeValue . '\']';
        $elements = $xpath->query( $q, $contextNode == null ? $DOM : $contextNode );
        return $elements;
    }

    private function fixWhiteSpace( $t ) {
        return trim( preg_replace( '/\s+/', ' ', $t ) );
    }

    function fetch_weather_data($zipcode)
    {
        $url = 'https://api.tomorrow.io/v4/weather/forecast?' . http_build_query([
                'location' => $zipcode . " US",
                'apikey' => \Drupal::config('kate_demo.settings')->get('tomorrowio_api_key'),
                'timesteps' => '1d',
                'units' => 'imperial',
                'fields' => 'temperatureMax,temperatureMin,weatherCode,weatherCodeFullDay'
            ]);

        echo $url;

        $weatherDescriptions = [
            1000 => 'Clear, Sunny',
            1100 => 'Mostly Clear',
            1101 => 'Partly Cloudy',
            1102 => 'Mostly Cloudy',
            1001 => 'Cloudy',
            2000 => 'Fog',
            2100 => 'Light Fog',
            4000 => 'Drizzle',
            4001 => 'Rain',
            4200 => 'Light Rain',
            4201 => 'Heavy Rain',
            5000 => 'Snow',
            5001 => 'Flurries',
            5100 => 'Light Snow',
            5101 => 'Heavy Snow',
            6000 => 'Freezing Drizzle',
            6001 => 'Freezing Rain',
            6200 => 'Light Freezing Rain',
            6201 => 'Heavy Freezing Rain',
            7000 => 'Ice Pellets',
            7101 => 'Heavy Ice Pellets',
            7102 => 'Light Ice Pellets',
            8000 => 'Thunderstorm'
        ];

        $ch = curl_init();
        curl_setopt($ch, CURLOPT_URL, $url);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);

        $response = curl_exec($ch);
        if ($response === false) {
            die('Curl error: ' . curl_error($ch));
        }
        curl_close($ch);

        $data = json_decode($response, true);

        if (!isset($data['timelines']['daily'])) {
            die("No forecast data found.\n");
        }

        $daysData = [];

        foreach ($data['timelines']['daily'] as $day) {
            $date = substr($day['time'], 0, 10);
            $tempMax = $day['values']['temperatureMax'];
            $tempMin = $day['values']['temperatureMin'];
            $code = $day['values']['weatherCodeFullDayMax'] ?? $day['values']['weatherCodeMax'] ?? null;
            $desc = $code && isset($weatherDescriptions[$code]) ? $weatherDescriptions[$code] : 'Unknown';

            $daysData[] = [
                'label' => $date,
                'high' => $tempMax,
                'low' => $tempMin,
                'phrase' => $desc,
            ];
        }

        $data[ 'location' ][ 'display_name' ] = & $data[ 'location' ][ 'name' ];

        return [
            'days' => $daysData,
            'location' => $data[ 'location' ],
        ];
    }

    /*
     * old code to get data from weather channel
     * 
	private function fetch_weather_data( $zipcode ) {
		$geocode = $this->geocode( $zipcode );
		$url_template = 'https://api.weather.com/v1/location/[ZIPCODE]:4:US/forecast/daily/7day.json?apiKey=6532d6454b8aa370768e63d6ba5a832e';
		$url = str_replace( array( '[ZIPCODE]', '[LAT]', '[LON]' ), array( $zipcode, $geocode[ 'lat' ], $geocode[ 'lon' ] ), $url_template );
		$raw = @file_get_contents( $url );
		$data = @json_decode( $raw );

		$parsed_data = array();
		foreach ( $data->forecasts as $day ) {
			$dayDate = date_create_from_format( 'Y-m-d\TH:i:sO', $day->fcst_valid_local );
			$label = date_format( $dayDate, 'j M' );

			// day->max_temp and the entire day->day structure for the current day apparently disappear after 4pm
			$this_day = array( $label,
				!empty( $day->max_temp ) ? $day->max_temp : $day->night->hi,
				$day->min_temp,
				isset( $day->day->phrase_22char ) ? $day->day->phrase_22char : $day->night->phrase_22char );

			$parsed_data[] = $this_day;
		}

		return array(
			'days' => $parsed_data,
			'location' => ( array )$geocode,
		);
	}
    */

	public function show_weather( $zipcode ) {
		$build = [];

		$data = $this->fetch_weather_data( $zipcode );
        $build[ '#title' ] = t( 'Weather forecast for @location', array( '@location' => $data[ 'location' ][ 'display_name' ] ) );

		$days = $data[ 'days' ];
		$header = array( t( 'Label' ), t( 'High' ), t( 'Low' ), t( 'Condition' ) );

		// so we can access this data in the code for the various integrations
        $session = \Drupal::service('tempstore.private')->get( 'my_session' );
        $session->set( 'kate_demo_data', $data );

        $days[] = array( array(
			'data' => t( 'Powered by @tomorrowiolink.', [
				'@tomorrowiolink' => \Drupal\Core\Link::fromTextAndUrl( 'Tomorrow.io',
					\Drupal\Core\Url::fromUri( 'https://www.tomorrow.io/' ) )->toString()
			] ),
			'colspan' => count( $header ),
		) );

		$weatherTableArray = [
			'#type' => 'table',
			'#header' => $header,
			'#rows' => $days,
			'#attributes' => [ 'id' => 'weatherTable' ]
		];

		$build[ '#markup' ] = '@weathertable';
		$return = &$build[ '#markup' ];

		$build[ '#attached' ][ 'placeholders' ] =
			[ '@weathertable' => $weatherTableArray ];

		$return .= '<p>Below is a dynamically generated SVG vector-graphic showing this forecast.  This image will remain crisp and clear even if you use your browser\'s zoom function to enlarge it!  To the right is an integrated Google Map of the location.</p>';

		$return .= '<div id=\'graphical-objects-container\'>';
		$return .= '<div id=\'weather-graph\'><img src=\'/kate_demo/640/300/graph_weather.svg\' /></div>';
		$return .= $this->openstreetmaps_leaflet_map( $data[ 'location' ], $build[ '#attached' ] );
		$return .= '<div style=\'clear: both;\'></div>';
		$return .= '</div>';

		$return .= '<div style=\'clear: both;\'></div>';

		$return .= '<div id=\'link-box\'>';

		$links = array();

		$links[]	= \Drupal\Core\Link::createFromRoute( 'Click here to export this data to a Google Spreadsheet!',
				'kate_demo.spreadsheet_export', [], [ 'attributes' => [ 'target' => '_blank' ] ] )->toString();

		//$links[] = $this->facebook_integration( $build[ '#attached' ] );

		$links[] = \Drupal\Core\Link::createFromRoute( 'Click here to download the code for this demo as a Drupal module!',
					'kate_demo.download_demo_code' )->toString()
			. t( " (generated dynamically from the actual live code!)" );

		if ( $this->ebay_access_callback() )
			$links[] = $this->get_ebay_link( $zipcode, $build[ '#attached' ] );

		$return .= join( '<br />', $links );

		$return .= '</div>';

		return $build;
	}
}
