/*
         * A function to handle the click event after the user clicks 'Add'
         */
        private void btnAdd_Click(object sender, EventArgs e)
        {
            // disable the button immediately
            this.btnAdd.Enabled = false;

            // create a download progress dialog and show it
            Dialogs.Processing.ProgressDialog downloadProgress = new Processing.ProgressDialog();
            downloadProgress.Show(this);

            // attempt to add the selected map to the user ArcGIS instance
            log.Debug("Attempting to create a new feature class and add it to the map.");
            try
            {
                // get the key out of the key/value pair object
                log.Debug("Fetching the user selected MapId from the DataGridView.");
                string MapId = ((MapsEngine.DataModel.gme.Map) this.dataGridGlobeDirectory.SelectedRows[0].DataBoundItem).id;
                log.Debug("MapId: " + MapId);

                // create a new empty Feature Class to hold the Google Maps Engine catalog results
                log.Debug("Creating a new empty feature class to hold the Google Maps Enigne catalog results");
                IFeatureClass fc = Extension.Data.GeodatabaseUtilities.createGoogleMapsEngineCatalogFeatureClass(ref log, ref ext);

                // publish a new download event to the extension
                ext.publishRaiseDownloadProgressChangeEvent(false, "Preparing to download map '" + MapId + "'.", 1, 0);

                // create a new reference to the Google Maps API.
                log.Debug("Creating a new instance of the Google Maps Engine API object.");
                MapsEngine.API.GoogleMapsEngineAPI api = new MapsEngine.API.GoogleMapsEngineAPI(ref log);

                // create a new map object to be defined by the API or MapRoot
                log.Debug("Creating a new empty Map object.");
                MapsEngine.DataModel.gme.Map map;

                // query Google Maps Engine for the layers within this map
                log.Debug("Fetching the Google Maps Engine layers for this map.");
                map = api.getMapById(ext.getToken(), MapId);

                // publish a new download event to the extension
                ext.publishRaiseDownloadProgressChangeEvent(false, "Building local geodatabase for map '" + MapId + "'.", 1, 0);

                // create a new Feature Class Management object
                Data.GoogleMapsEngineFeatureClassManagement fcMngmt = new Data.GoogleMapsEngineFeatureClassManagement(api);

                // populate a feature for every Google Maps Engine Map
                log.Debug("Populate the feature class with the specific MapId");
                fcMngmt.populateFCWithGoogleMapsEngineMap(ref fc, ref map);

                // publish a new download event to the extension
                ext.publishRaiseDownloadProgressChangeEvent(true, "Adding '" + MapId + "' layer to your map.", 1, 1);

                // add the new feature class to the map (auto selecting type "map")
                ext.addFeatureClassToMapAsLayer(ref fc, Properties.Resources.GeodatabaseUtilities_schema_LayerName
                                                , "" + Properties.Resources.GeodatabaseUtilities_schema_AssetType_Name + " = 'map'");

                // retrieve the Google Maps Engine WMS URL
                string url = Properties.Settings.Default.gme_wms_GetCapabilities;

                // replace the map identifier and auth token
                url = url.Replace("{mapId}", MapId);
                url = url.Replace("{authTokenPlusSlash}", ext.getToken().access_token + "/");

                // proactively add the viewable layer (WMS or WMTS) to the map
                ext.addWebMappingServiceToMap(new Uri(url));
            }
            catch (System.Exception ex)
            {
                // log error and warn user
                log.Error(ex);

                // hide the download progress, if it is visible
                downloadProgress.Hide();

                // warn the user of the error
                ext.displayErrorDialog(Properties.Resources.dialog_GoogleMapsEngineDirectoryListing_btnAdd_Click_unknownerror + "\n" + ex.Message);
            }

            // close the dialog immediately
            this.Close();
        }
        /*
         * Makes a request to the Google Maps Engine API and returns the
         * response from the API in a string object.
         */
        private String makeGoogleMapsEngineRequest(String RequestUrl, String access_token)
        {
            // create a random number generator to handle exponential backoff
            Random randomGenerator = new Random();

            // start a look, attempting no more than N attempts
            for (int n = 0; n < 5; ++n)
            {
                // attempt to make request, catching error
                try
                {
                    // setup an HTTP Web Request method
                    log.Debug("Creating a new WebRequest object.");
                    WebRequest apiWebRequest = HttpWebRequest.Create(RequestUrl);

                    // set the request method to GET
                    apiWebRequest.Method = "GET";

                    // set the content type
                    apiWebRequest.ContentType = "application/json";

                    // set the OAuth 2.0 Access token
                    log.Debug("Setting the OAuth 2.0 Authorization Header paramter.");
                    apiWebRequest.Headers.Add("Authorization", "OAuth " + access_token);

                    // set the user agent, for trouble shooting
                    log.Debug("Setting the user agent for this request for identification.");
                    ((HttpWebRequest)apiWebRequest).UserAgent = "Google Maps Engine Connector for ArcGIS";

                    // publish a new download event to the extension (if DEBUG)
                    if (log.IsDebugEnabled)
                    {
                        ext.publishRaiseDownloadProgressChangeEvent(false, "Requesting " + RequestUrl);
                    }

                    // get the HTTP response
                    log.Debug("Retrieving the HTTP response.");
                    using (WebResponse apiWebResponse = apiWebRequest.GetResponse())
                    {
                        // verify the response status was OK
                        log.Debug("Verifying the Server response was OK.");
                        if (((HttpWebResponse)apiWebResponse).StatusCode == HttpStatusCode.OK)
                        {
                            log.Debug("Server response was OK.");

                            // setup a stream reader to read the response from the server
                            log.Debug("Streaming the server response.");
                            System.IO.StreamReader reader = new System.IO.StreamReader(apiWebResponse.GetResponseStream());

                            // read the response into a local variable
                            log.Debug("Reading the server response to end.");
                            string jsonResponse = reader.ReadToEnd();

                            // close the response stream from the server
                            log.Debug("Closing the server response.");
                            apiWebResponse.Close();

                            // if debug, log the server response
                            if (log.IsDebugEnabled && debugDirectory != null)
                            {
                                try
                                {
                                    // cast the RequestUrl into a URI object
                                    Uri requestUri = new Uri(RequestUrl);

                                    // create a stream writer and new text file
                                    System.IO.StreamWriter sw = System.IO.File.CreateText(
                                        debugDirectory.FullName
                                        + "\\"
                                        + (requestUri.PathAndQuery.Replace("/", "_").Replace("?", "_").Replace("&", "_"))
                                        + ".txt");

                                    // write the json response
                                    sw.Write(jsonResponse);

                                    // flush the writer
                                    sw.Flush();

                                    // close the writer
                                    sw.Close();
                                }
                                catch (Exception) { }
                            }

                            // return the response object
                            log.Debug("Returning the response Json");
                            return(jsonResponse);
                        }
                        else if (((HttpWebResponse)apiWebResponse).StatusCode == HttpStatusCode.ServiceUnavailable)
                        {
                            // the service was not available, attempt to recover
                            //throw new System.Net.WebException("ServiceUnavailable");
                            // Apply exponential backoff.
                            ext.publishRaiseDownloadProgressChangeEvent(false, "Applying exponential backoff (" + ((HttpWebResponse)apiWebResponse).StatusDescription + ")");
                            Thread.Sleep((1 << n) * 1000 + randomGenerator.Next(1001));
                        }
                        else
                        {
                            // an error occured, throw an exception
                            //throw new System.Exception(Properties.Resources.GoogleMapsEngineAPI_webrequest_unknownexception);
                            // Apply exponential backoff.
                            ext.publishRaiseDownloadProgressChangeEvent(false, "Applying exponential backoff (" + ((HttpWebResponse)apiWebResponse).StatusDescription + ")");
                            Thread.Sleep((1 << n) * 1000 + randomGenerator.Next(1001));
                        }
                    }
                }
                catch (Exception ex)
                {
                    // an exception has occured, log and throw
                    log.Error(ex);

                    // throw an exception
                    throw new System.Exception(Properties.Resources.GoogleMapsEngineAPI_webrequest_unknownexception);
                }
            }

            // An error occured, return nothing
            return(null);
        }
        protected void uploadButtonClicked(String projectId, String name, String description, String tags, String aclName, String attribution)
        {
            try
            {
                // create a new Processing Dialog
                Dialogs.Processing.ProgressDialog processingDialog = new Processing.ProgressDialog();

                // check to see if the layer is valid
                // and destination is configured correctly
                if (isLayerValidated &&
                    projectId != null && projectId.Length > 0 &&
                    name != null && name.Length > 0 &&
                    aclName != null && aclName.Length > 0)
                {
                    // show the processing dialog
                    processingDialog.Show();

                    // create a reference to the Google Maps Engine API
                    MapsEngine.API.GoogleMapsEngineAPI api = new MapsEngine.API.GoogleMapsEngineAPI(ref log);

                    // establish a reference to the running ArcMap instance
                    ESRI.ArcGIS.ArcMapUI.IMxDocument mxDoc = (ESRI.ArcGIS.ArcMapUI.IMxDocument)ArcMap.Application.Document;

                    // retrieve a reference to the selected layer
                    ESRI.ArcGIS.Carto.ILayer selectedLayer = mxDoc.SelectedLayer;

                    // create a temporary working directory
                    System.IO.DirectoryInfo tempDir
                        = System.IO.Directory.CreateDirectory(ext.getLocalWorkspaceDirectory() + "\\" + System.Guid.NewGuid());

                    // determine what type of layer is selected
                    if (selectedLayer is ESRI.ArcGIS.Carto.IFeatureLayer)
                    {
                        // export a copy of the feature class to a "uploadable" Shapefile
                        // raise a processing notification
                        ext.publishRaiseDownloadProgressChangeEvent(false, "Extracting a copy of '" + selectedLayer.Name + "' (feature class) for data upload.");
                        ExportLayerToShapefile(tempDir.FullName, selectedLayer.Name, selectedLayer);

                        processingDialog.Update();
                        processingDialog.Focus();

                        // create a list of files in the temp directory
                        List <String> filesNames = new List <String>();
                        for (int k = 0; k < tempDir.GetFiles().Count(); k++)
                        {
                            if (!tempDir.GetFiles()[k].Name.EndsWith(".lock"))
                            {
                                filesNames.Add(tempDir.GetFiles()[k].Name);
                            }
                        }

                        // create a Google Maps Engine asset record
                        ext.publishRaiseDownloadProgressChangeEvent(false, "Requesting a new Google Maps Engine asset be created.");
                        MapsEngine.DataModel.gme.UploadingAsset uploadingAsset
                            = api.createVectorTableAssetForUploading(ext.getToken(),
                                                                     MapsEngine.API.GoogleMapsEngineAPI.AssetType.table,
                                                                     projectId,
                                                                     name,
                                                                     aclName,
                                                                     filesNames,
                                                                     description,
                                                                     tags.Split(",".ToCharArray()).ToList <String>(),
                                                                     "UTF-8");

                        // Initiate upload of file(s)
                        ext.publishRaiseDownloadProgressChangeEvent(false, "Starting to upload files...");
                        api.uploadFilesToAsset(ext.getToken(), uploadingAsset.id, tempDir.GetFiles());

                        // launch a web browser
                        ext.publishRaiseDownloadProgressChangeEvent(false, "Launching a web browser.");
                        System.Diagnostics.Process.Start("https://mapsengine.google.com/admin/#RepositoryPlace:cid=" + projectId + "&v=DETAIL_INFO&aid=" + uploadingAsset.id);
                    }
                    else if (selectedLayer is ESRI.ArcGIS.Carto.IRasterLayer)
                    {
                        // export a copy of the raster to a format that can be uploaded
                        // raise a processing notification
                        ext.publishRaiseDownloadProgressChangeEvent(false, "Extracting a copy of '" + selectedLayer.Name + "' (raster) for data upload.");
                        ExportLayerToUploadableRaster(tempDir.FullName, tempDir.Name, selectedLayer);

                        processingDialog.Update();
                        processingDialog.Focus();

                        // create a list of files in the temp directory
                        List <String> filesNames = new List <String>();
                        for (int k = 0; k < tempDir.GetFiles().Count(); k++)
                        {
                            if (!tempDir.GetFiles()[k].Name.EndsWith(".lock"))
                            {
                                filesNames.Add(tempDir.GetFiles()[k].Name);
                            }
                        }

                        // create a Google Maps Engine asset record
                        ext.publishRaiseDownloadProgressChangeEvent(false, "Requesting a new Google Maps Engine asset be created.");
                        MapsEngine.DataModel.gme.UploadingAsset uploadingAsset
                            = api.createRasterAssetForUploading(ext.getToken(),
                                                                projectId,
                                                                name,
                                                                aclName,
                                                                attribution, // attribution
                                                                filesNames,
                                                                description,
                                                                tags.Split(",".ToCharArray()).ToList <String>());

                        // Initiate upload of file(s)
                        ext.publishRaiseDownloadProgressChangeEvent(false, "Starting to upload files...");
                        api.uploadFilesToAsset(ext.getToken(), uploadingAsset.id, tempDir.GetFiles());

                        // launch a web browser
                        ext.publishRaiseDownloadProgressChangeEvent(false, "Launching a web browser.");
                        System.Diagnostics.Process.Start("https://mapsengine.google.com/admin/#RepositoryPlace:cid=" + projectId + "&v=DETAIL_INFO&aid=" + uploadingAsset.id);
                    }

                    // Delete temporary directory and containing files
                    // TODO: Remove temporary files, but can't remove them at the moment because they are in use by
                    // the ArcMap seesion.
                    //tempDir.Delete(true);

                    // close the processing dialog
                    ext.publishRaiseDownloadProgressChangeEvent(true, "Finished");
                    processingDialog.Close();
                }
                else
                {
                    // display an error dialog to the user
                    System.Windows.Forms.MessageBox.Show("The selected layer is invalid or the destination configuration has not been properly set.");
                }
            }
            catch (System.Exception ex)
            {
                // Ops.
                System.Windows.Forms.MessageBox.Show("An error occured. " + ex.Message);
            }

            // close the dialog
            this.Close();
        }