/*
         * Method: GetPlacesDetailWithOptions
         *
         * Description: This method can be used to query the Places API for details regarding places with a
         *   specific place_id. The optional parameters can be used to determine the language of the response
         *   as well as what infromation is returned. If no fields are provided, then the query returns all
         *   available fields.
         *
         * Parameters:
         *   - place_id (String): A String identifier that uniquely identifies a place. This is returned as part
         *       of the response from the Place Search functions. For more details about place_id:
         *       https://developers.google.com/places/web-service/place-id
         *
         *   - region_code (String): This is an OPTIONAL parameter, that indicates the region code, specified as a
         *       ccTLD format. This is used to influence the query's results but relevant results outside the
         *       region may also be included.
         *   - fields (List<PlacesDetailFields>): OPTIONAL parameter. This is a list of details you wish to get
         *       about the places that match the query. If the list is empty or null, then the Places API will
         *       return all available details by default.
         *   - language_code (String): OPTIONAL parameter indicating the language in which results will be returned.
         *       By default this is set to English. List of supported languages and their codes:
         *       https://developers.google.com/maps/faq#languagesupport
         *
         *   - APIKey (String): Implicity required paramter which should be set through the constructor when
         *       creating an object of this class. For more details about the Google API Key please see:
         *       https://developers.google.com/places/web-service/get-api-key
         *
         * Return: The method returns a tuple of two items. The first is an object of PlacesDetailResponse
         *   which contains all available details for the place. The second element is a ResponseStatus object
         *   indicating the status of the query along with the appropiate HTTP code. The tuple wrapped in a Task<>
         *   because the method makes Asynchronous HTTP requests to the Places API.
         */
        public async Task <Tuple <PlacesDetailResponse, ResponseStatus> > GetPlaceDetailsWithOptions(String place_id,
                                                                                                     String region_code = "", String language_code = "", String session_token = "", List <PlacesDetailFields> fields = null)
        {
            if (BasicFunctions.isEmpty(APIKey))
            {
                return(new Tuple <PlacesDetailResponse, ResponseStatus>(null, PlacesStatus.MISSING_API_KEY));
            }
            if (BasicFunctions.isEmpty(place_id))
            {
                return(new Tuple <PlacesDetailResponse, ResponseStatus>(null, PlacesStatus.MISSING_PLACE_ID));
            }

            // Creating the HTTP query url
            String HTTP_query = $"details/json?placeid={place_id}";

            // Appending any optional fields that are set
            if (!BasicFunctions.isEmpty(region_code))
            {
                HTTP_query += $"&region={region_code}";
            }
            if (!BasicFunctions.isEmpty(language_code))
            {
                HTTP_query += $"&language={language_code}";
            }
            if (!BasicFunctions.isEmpty(session_token))
            {
                HTTP_query += $"&sessiontoken={session_token}";
            }
            if (fields != null && fields.Count != 0)
            {
                HTTP_query += $"&fields={BasicFunctions.getPlacesDetailFieldsListString(fields)}";
            }
            HTTP_query += $"&key={APIKey}";

            // Setting up the request header to indicate that the request body will be in json
            httpClient.DefaultRequestHeaders.Accept.Clear();
            httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));

            // Making an asynchronous HTTP GET request to the Places API and collecting the output
            HttpResponseMessage response = await httpClient.GetAsync(HTTP_query);

            Stream stream = await response.Content.ReadAsStreamAsync();

            StreamReader streamReader = new StreamReader(stream);
            String       response_str = streamReader.ReadToEnd();

            // Similar two-step hop as in prior functions
            if (response.IsSuccessStatusCode)
            {
                try {
                    PlacesDetailResponse resultList = JsonConvert.DeserializeObject <PlacesDetailResponse>(response_str);
                    if (!resultList.Status.Equals("OK"))
                    {
                        // If the response status from the API is not OK, then we try to return the most appropriate Error
                        ResponseStatus status = PlacesStatus.ProcessErrorMessage(resultList.Status, resultList.Error_message);
                        return(new Tuple <PlacesDetailResponse, ResponseStatus>(null, status));
                    }
                    else if (resultList.Result == null)
                    {
                        // If the response provides an empty response set, then we return the ZERO_RESULTS (204) error
                        return(new Tuple <PlacesDetailResponse, ResponseStatus>(null, PlacesStatus.ZERO_RESULTS));
                    }
                    else
                    {
                        return(new Tuple <PlacesDetailResponse, ResponseStatus>(resultList, PlacesStatus.OK));
                    }
                } catch (JsonSerializationException e) {
                    // If the deserialization of the response fails, then we return an error
                    Debug.WriteLine("Exception: " + e.StackTrace);
                    return(new Tuple <PlacesDetailResponse, ResponseStatus>(null, PlacesStatus.DESERIALIZATION_ERROR));
                }
            }
            else
            {
                // If the response status from the API is not a success, then we return an error using the data returned
                return(new Tuple <PlacesDetailResponse, ResponseStatus>(null, new ResponseStatus((int)response.StatusCode, response.ReasonPhrase)));
            }
        }
        /*
         * Method: GetPlacesDetail
         *
         * Description: This method can be used to query the Places API for details regarding places with a
         *   specific place_id. The place_id is usually obtained from the response of a Place Search function.
         *
         * Parameters:
         *   - place_id (String): A String identifier that uniquely identifies a place. This is returned as part
         *       of the response from the Place Search functions. For more details about place_id:
         *       https://developers.google.com/places/web-service/place-id
         *
         *   - APIKey (String): Implicity required paramter which should be set through the constructor when
         *       creating an object of this class. For more details about the Google API Key please see:
         *       https://developers.google.com/places/web-service/get-api-key
         *
         * Return: The method returns a tuple of two items. The first is an object of PlacesDetailResponse
         *   which contains all available details for the place. The second element is a ResponseStatus object
         *   indicating the status of the query along with the appropiate HTTP code. The tuple wrapped in a Task<>
         *   because the method makes Asynchronous HTTP requests to the Places API.
         */
        public async Task <Tuple <PlacesDetailResponse, ResponseStatus> > GetPlaceDetails(String place_id)
        {
            if (BasicFunctions.isEmpty(APIKey))
            {
                return(new Tuple <PlacesDetailResponse, ResponseStatus>(null, PlacesStatus.MISSING_API_KEY));
            }
            if (BasicFunctions.isEmpty(place_id))
            {
                return(new Tuple <PlacesDetailResponse, ResponseStatus>(null, PlacesStatus.MISSING_PLACE_ID));
            }

            // Creating the HTTP query url
            String HTTP_query = $"details/json?placeid={place_id}&key={APIKey}";

            // Setting up the request header to indicate that the request body will be in json
            httpClient.DefaultRequestHeaders.Accept.Clear();
            httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));

            // Making an asynchronous HTTP GET request to the Places API and collecting the output
            HttpResponseMessage response = await httpClient.GetAsync(HTTP_query);

            Stream stream = await response.Content.ReadAsStreamAsync();

            StreamReader streamReader = new StreamReader(stream);
            String       response_str = streamReader.ReadToEnd();

            Console.WriteLine(response_str);

            /*
             * Here we do a two-step hop again to achieve the appropriate return value:
             *   We use the response string (response_str) from above and attempt to convert it back from json to
             *   NearbySearchResultList, the expected return object for a successful query. This produces one of
             *   two possibilities:
             *   1. If the response string is not a json of the NearbySearchResultList class, then we either get
             *      a JsonSerializationException or an empty list. In this case we print out the response and
             *      return null (Will improve this to return an appropriate error code).
             *   2. If the response string is as expected a json of NearbySearchResultList, then things go
             *      smoothly and we return that.
             */
            if (response.IsSuccessStatusCode)
            {
                try {
                    PlacesDetailResponse resultList = JsonConvert.DeserializeObject <PlacesDetailResponse>(response_str);
                    if (!resultList.Status.Equals("OK"))
                    {
                        // If the response status from the API is not OK, then we try to return the most appropriate Error
                        ResponseStatus status = PlacesStatus.ProcessErrorMessage(resultList.Status, resultList.Error_message);
                        return(new Tuple <PlacesDetailResponse, ResponseStatus>(null, status));
                    }
                    else if (resultList.Result == null)
                    {
                        // If the response provides an empty response set, then we return the ZERO_RESULTS (204) error
                        return(new Tuple <PlacesDetailResponse, ResponseStatus>(null, PlacesStatus.ZERO_RESULTS));
                    }
                    else
                    {
                        return(new Tuple <PlacesDetailResponse, ResponseStatus>(resultList, PlacesStatus.OK));
                    }
                } catch (JsonSerializationException e) {
                    // If the deserialization of the response fails, then we return an error
                    Console.WriteLine("Exception: " + e.StackTrace);
                    return(new Tuple <PlacesDetailResponse, ResponseStatus>(null, PlacesStatus.DESERIALIZATION_ERROR));
                }
            }
            else
            {
                // If the response status from the API is not a success, then we return an error using the data returned
                return(new Tuple <PlacesDetailResponse, ResponseStatus>(null, new ResponseStatus((int)response.StatusCode, response.ReasonPhrase)));
            }
        }
        /*
         * Method: GetPlacesPhotos
         *
         * Description: This method can be used to get a photo based on a photo reference returned as part of a
         *   PlacesSearch or PlacesDetail response. If the photo exists, then the method will save the photo at
         *   the desired directory address.
         *
         * Parameters:
         *   - photoReference (String): A String identifier that uniquely identifies a photo. This is returned as
         *       part of the response for a PlacesSearch or PlacesDetail queries.
         *   - fileDestination (String): An absolute or relative path address of the desired location where the
         *       photo should be stored.
         *
         *  One of the following two parameters is required:
         *   - maxHeight (int): This is an OPTIONAL parameter which indicates the maximum height of the image.
         *   - maxWidth (int): This is an OPTIONAL parameter which indicates the maximum width of the image.
         *
         *   - APIKey (String): Implicity required paramter which should be set through the constructor when
         *       creating an object of this class. For more details about the Google API Key please see:
         *       https://developers.google.com/places/web-service/get-api-key
         *
         * Return: The method returns a tuple of two items. The first is a String. If the query is successful
         *   then the string returns the directory location where the photo was stored. If the query fails for any
         *   reason then, the string is returned as null. The second element is a ResponseStatus object indicating
         *   the status of the query along with the appropiate HTTP code. The tuple wrapped in a Task<> because
         *   the method makes Asynchronous HTTP requests to the Places API.
         */
        public async Task <Tuple <String, ResponseStatus> > GetPlacesPhotos(String photoReference,
                                                                            String fileDestination, int maxHeight = 0, int maxWidth = 0)
        {
            if (BasicFunctions.isEmpty(APIKey))
            {
                return(new Tuple <String, ResponseStatus>(null, PlacesStatus.MISSING_API_KEY));
            }
            if (BasicFunctions.isEmpty(photoReference))
            {
                return(new Tuple <String, ResponseStatus>(null, PlacesStatus.MISSING_PHOTO_REFERENCE));
            }
            if (maxHeight <= 0 && maxWidth <= 0)
            {
                return(new Tuple <string, ResponseStatus>(null, PlacesStatus.MISSING_HEIGHT_WIDTH));
            }
            if (BasicFunctions.isEmpty(fileDestination))
            {
                return(new Tuple <string, ResponseStatus>(null, PlacesStatus.MISSING_FILE_DESTINATION));
            }

            // Creating the HTTP query url
            String HTTP_query = $"photo?photoreference={photoReference}";

            if (maxHeight > 0)
            {
                HTTP_query += $"&maxheight={maxHeight}";
            }
            if (maxWidth > 0)
            {
                HTTP_query += $"&maxwidth={maxWidth}";
            }
            HTTP_query += $"&key={APIKey}";

            // Setting up the request header to indicate that the request body will be in json
            httpClient.DefaultRequestHeaders.Accept.Clear();
            httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));

            // Making an asynchronous HTTP GET request to the Places API and collecting the output
            HttpResponseMessage response = await httpClient.GetAsync(HTTP_query);

            Stream stream = await response.Content.ReadAsStreamAsync();

            if (response.IsSuccessStatusCode)
            {
                // The following block of code is borrowed from https://stackoverflow.com/a/2368180

                using (BinaryReader reader = new BinaryReader(stream)) {
                    Byte[] lnByte = reader.ReadBytes(1 * 1024 * 1024 * 10);
                    try {
                        using (FileStream lxFS = new FileStream(fileDestination, FileMode.Create)) {
                            lxFS.Write(lnByte, 0, lnByte.Length);

                            // End of borrowed code
                        }
                    } catch (ArgumentException e) {
                        // If we get an exception, then the directory path provided is likely invalid and we return that error
                        Debug.WriteLine(e.StackTrace);
                        return(new Tuple <String, ResponseStatus>(null, PlacesStatus.INVALID_FILE_LOCATION));
                    } catch (IOException e) {
                        Debug.WriteLine(e.StackTrace);

                        if (e.Message.ToLower().Contains("could not find a part of the path"))
                        {
                            return(new Tuple <string, ResponseStatus>(null, PlacesStatus.INVALID_FILE_LOCATION));
                        }
                        return(new Tuple <String, ResponseStatus>(null, PlacesStatus.INTERNAL_SERVER_ERROR));
                    }
                }
            }
            else
            {
                return(new Tuple <string, ResponseStatus>(null, PlacesStatus.ProcessErrorMessage(response.StatusCode.ToString(), response.ReasonPhrase)));
            }

            // If there are no errors, then we return the directory address where the photo was stored
            return(new Tuple <string, ResponseStatus>(fileDestination, PlacesStatus.OK));
        }