void ISerializationCallbackReceiver.OnAfterDeserialize()
 {
     if (_serviceProvider_serialized != null && _serviceProvider_serialized.Length != 0)
     {
         var type = System.Type.GetType(_serviceProvider_serialized);
         _serviceProvider = System.Activator.CreateInstance(type) as IElevationServiceProvider;
     }
 }
        void OnGUI()
        {
            _isReady    = _mainTask == null || _mainTask.IsCompleted;
            GUI.enabled = _isReady;

            EditorGUILayout.Separator();

            EditorGUILayout.BeginVertical();
            {
                EditorGUILayout.BeginHorizontal();
                GUILayout.Label("Service Provider Selected: ");
                GUILayout.Label(_serviceProvider != null ? _serviceProvider.GetType().Name : "NONE");
                EditorGUILayout.EndHorizontal();
                foreach (var entry in _listServiveProviders)
                {
                    if (
                        _serviceProvider.GetType() != entry.instance.GetType() &&
                        GUILayout.Button($"Switch To { entry.name }")
                        )
                    {
                        _serviceProvider = entry.instance;
                        _key             = null;
                    }
                }
            }
            EditorGUILayout.EndVertical();

            _scroll_window = EditorGUILayout.BeginScrollView(_scroll_window);
            {
                GUILayout.BeginVertical("- SETTINGS -", "window");
                {
                    EditorGUILayout.BeginHorizontal();
                    {
                        GUILayout.Label("REGION BOTTOM LEFT CORNER:");

                        //GUILayout.FlexibleSpace();

                        GUILayout.Label("latitude:", GUILayout.Width(60f));
                        _settings.start.latitude = Mathf.Clamp(
                            EditorGUILayout.FloatField(_settings.start.latitude, GUILayout.Width(60f)),
                            -90f,
                            90f
                            );

                        GUILayout.Label("longitude:", GUILayout.Width(60f));
                        _settings.start.longitude = Mathf.Clamp(
                            EditorGUILayout.FloatField(_settings.start.longitude, GUILayout.Width(60f)),
                            -180f,
                            180f
                            );
                    }
                    EditorGUILayout.EndHorizontal();
                    EditorGUILayout.BeginHorizontal();
                    {
                        GUILayout.Label("REGION UPPER RIGHT CORNER:");

                        //GUILayout.FlexibleSpace();

                        GUILayout.Label("latitude:", GUILayout.Width(60f));
                        _settings.end.latitude = Mathf.Clamp(
                            EditorGUILayout.FloatField(_settings.end.latitude, GUILayout.Width(60f)),
                            -90f,
                            90f
                            );

                        GUILayout.Label("longitude:", GUILayout.Width(60f));
                        _settings.end.longitude = Mathf.Clamp(
                            EditorGUILayout.FloatField(_settings.end.longitude, GUILayout.Width(60f)),
                            -180f,
                            180f
                            );
                    }
                    EditorGUILayout.EndHorizontal();
                    EditorGUILayout.BeginHorizontal();
                    {
                        GUILayout.FlexibleSpace();
                        GUILayout.Label("(move region)");
                        float w = _settings.end.longitude - _settings.start.longitude;
                        float h = _settings.end.latitude - _settings.start.latitude;
                        if (GUILayout.Button("<", GUILayout.Width(30f)))
                        {
                            _settings.start.longitude -= w;
                            _settings.end.longitude   -= w;
                        }
                        if (GUILayout.Button("^", GUILayout.Width(30f)))
                        {
                            _settings.start.latitude += h;
                            _settings.end.latitude   += h;
                        }
                        if (GUILayout.Button("v", GUILayout.Width(30f)))
                        {
                            _settings.start.latitude -= h;
                            _settings.end.latitude   -= h;
                        }
                        if (GUILayout.Button(">", GUILayout.Width(30f)))
                        {
                            _settings.start.longitude += w;
                            _settings.end.longitude   += w;
                        }
                    }
                    EditorGUILayout.EndHorizontal();
                    EditorGUILayout.BeginHorizontal();
                    {
                        EditorGUILayout.PrefixLabel("Resolution:");

                        //GUILayout.FlexibleSpace();

                        //calculate meters per degree:
                        double metersPerDegreeLatitude;
                        double metersPerDegreeLongitude;
                        {
                            Coordinate middlePoint = _settings.start + ((_settings.end - _settings.start) * 0.5f);
                            metersPerDegreeLatitude = _core.HaversineDistance(
                                new Coordinate {
                                latitude  = _settings.start.latitude,
                                longitude = middlePoint.longitude
                            },
                                new Coordinate {
                                latitude  = _settings.end.latitude,
                                longitude = middlePoint.longitude
                            }
                                ) / _settings.resolution.latitude;
                            metersPerDegreeLongitude = _core.HaversineDistance(
                                new Coordinate {
                                latitude  = middlePoint.latitude,
                                longitude = _settings.start.longitude
                            },
                                new Coordinate {
                                latitude  = middlePoint.latitude,
                                longitude = _settings.end.longitude
                            }
                                ) / _settings.resolution.longitude;
                        }

                        GUILayout.Label("latitude:", GUILayout.Width(60f));
                        _settings.resolution.latitude = Mathf.Clamp(
                            EditorGUILayout.IntField(_settings.resolution.latitude, GUILayout.Width(60f)),
                            1,
                            int.MaxValue
                            );
                        GUILayout.Label($"({ metersPerDegreeLatitude.ToString("0.##") } [m])");

                        GUILayout.Label("longitude:", GUILayout.Width(60f));
                        _settings.resolution.longitude = Mathf.Clamp(
                            EditorGUILayout.IntField(_settings.resolution.longitude, GUILayout.Width(60f)),
                            1,
                            int.MaxValue
                            );
                        GUILayout.Label($"({ metersPerDegreeLongitude.ToString("0.##") } [m])");
                    }
                    EditorGUILayout.EndHorizontal();

                    EditorGUILayout.BeginHorizontal();
                    {
                        GUILayout.Label("Max Coordinates Per Request");
                        settings.maxCoordinatesPerRequest = EditorGUILayout.IntField(settings.maxCoordinatesPerRequest);
                    }
                    EditorGUILayout.EndHorizontal();
                }
                GUILayout.EndVertical();



                GUILayout.FlexibleSpace();



                GUILayout.BeginVertical("- PROCESS -", "window");
                {
                    GUILayout.BeginHorizontal();
                    GUILayout.Label("api key: ", GUILayout.ExpandWidth(false));
                    _key = GUILayout.PasswordField(_key != null ? _key : "", '*');
                    GUILayout.EndHorizontal();

                    //start button:
                    if (GUILayout.Button("START HTTP REQUESTS", GUILayout.Height(EditorGUIUtility.singleLineHeight * 2f)))
                    {
                        string filePath = EditorUtility.SaveFilePanel(
                            $"Save data file",
                            _core.GetFolderPath(),
                            _core.GetFileNamePrefix(
                                _settings.start,
                                _settings.end,
                                _settings.resolution
                                ),
                            "csv"
                            );
                        if (filePath.Length != 0)
                        {
                            _taskTicket = new Ticket <float>(0f);
                            _mainTask   = _core.GetElevationData(
                                filePath:                       filePath,
                                serviceProvider:                _serviceProvider,
                                apikey:                         _key,
                                ticket:                         _taskTicket,
                                start:                          _settings.start,
                                end:                            _settings.end,
                                resolution:                     _settings.resolution,
                                maxCoordinatesPerRequest:       _settings.maxCoordinatesPerRequest,
                                this.Repaint,
                                () =>
                            {
                                if (_onFinished == EOnFinished.createImage)
                                {
                                    _core.WriteImageFile(
                                        filePath.Replace(".csv", ".png"),
                                        _settings.resolution.longitude,
                                        _settings.resolution.latitude,
                                        _createImageSettings.offset,
                                        _createImageSettings.lerp
                                        );
                                }

                                //flash editor window (will it even does that?)
                                EditorWindow.GetWindow <MainWindow>().Show();
                            },
                                _logTraffic
                                );
                        }
                        else
                        {
                            Debug.Log("Cancelled by user");
                        }
                    }

                    bool working = _mainTask != null && _mainTask.Status != TaskStatus.RanToCompletion && _mainTask.Status != TaskStatus.Canceled;

                    //progress bar:
                    if (working)
                    {
                        bool b = GUI.enabled;
                        GUI.enabled = true;
                        EditorGUI.ProgressBar(EditorGUILayout.GetControlRect(), _taskTicket.value, "progress");
                        GUI.enabled = b;
                    }

                    //abort button:
                    bool abortingInProgress = working && _taskTicket.invalid;
                    GUI.enabled = _isReady == false && abortingInProgress == false;
                    {
                        string abortButtonLabel = abortingInProgress ? "Aborting..." : "Abort";
                        if (GUILayout.Button(abortButtonLabel))
                        {
                            _taskTicket.Invalidate();
                        }
                    }
                    GUI.enabled = _isReady;

                    //toggles
                    {
                        bool GUIenabled = GUI.enabled;
                        GUI.enabled = true;
                        {
                            //log traffic toggle:
                            _logTraffic = EditorGUILayout.Toggle("log traffic:", _logTraffic);

                            //do on finished:
                            _onFinished = (EOnFinished)EditorGUILayout.EnumFlagsField("On Finished:", _onFinished);
                        }
                        GUI.enabled = GUIenabled;
                    }
                }
                GUILayout.EndVertical();



                GUILayout.FlexibleSpace();



                GUILayout.BeginVertical("- TOOLS -", "window");
                {
                    //tools are (should be) independent from http process, so make sure GUI is enabled:
                    GUI.enabled = true;

                    //
                    if (GUILayout.Button("Create Image", GUILayout.Height(EditorGUIUtility.singleLineHeight * 2f)))
                    {
                        CreateImageWindow.CreateWindow(this);
                    }
                }
                GUILayout.EndVertical();
                EditorGUILayout.Separator();
            }
            EditorGUILayout.EndScrollView();
            EditorGUILayout.Separator();
        }
        public async Task <string> HttpRequest
        (
            IElevationServiceProvider serviceProvider,
            Stack <Coordinate> coordinates,
            string apikey,
            int maxCoordinatesPerRequest,
            Ticket ticket,
            bool logTraffic
        )
        {
            //assertions:
            if (coordinates.Count == 0)
            {
                Debug.LogWarning("coordinates.Count is 0");
                return(null);
            }

            //
            List <Coordinate> requestList = new List <Coordinate>();
            //const int lengthLimit = 102375;//(bytes) this number is guesstimation, i found errors but no documentation on this
            int limit = maxCoordinatesPerRequest;//2200;//guesstimation

            for (int i = 0; i < limit && coordinates.Count != 0; i++)
            {
                requestList.Add(coordinates.Pop());
            }

            //create the HttpContent for the form to be posted:
            System.Net.Http.ByteArrayContent requestContent = null;
            if (serviceProvider.httpMethod == HttpMethod.Post)
            {
                string json   = serviceProvider.GetRequestContent(requestList);
                byte[] buffer = System.Text.Encoding.ASCII.GetBytes(json);
                requestContent = new ByteArrayContent(buffer);
                requestContent.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("application/json");
                //Debug.Log( "requestContent.Headers.ContentLength.Value: " + requestContent.Headers.ContentLength.Value );

                //log request:
                if (logTraffic)
                {
                    Debug.Log($"requesting { requestList.Count } values:\n{ json }");
                }
            }

            //get the stream of the content.
            string result = null;

            while (result == null && ticket.valid)
            {
                try
                {
                    HttpResponseMessage response;

                    if (serviceProvider.httpMethod == HttpMethod.Post)
                    {
                        response = await _client.PostAsync(
                            serviceProvider.RequestUri(null, apikey),
                            requestContent
                            );
                    }
                    else if (serviceProvider.httpMethod == HttpMethod.Get)
                    {
                        response = await _client.GetAsync(
                            serviceProvider.RequestUri(serviceProvider.GetRequestContent(requestList), apikey)
                            );
                    }
                    else
                    {
                        throw new System.NotImplementedException();
                    }

                    result = await response.Content.ReadAsStringAsync();

                    if (result.StartsWith("<html>"))
                    {
                        const string ERROR_504 = "504 Gateway Time-out";
                        const string ERROR_502 = "502 Bad Gateway";
                        const string ERROR_500 = "500 Internal Server Error";

                        //log warning:
                        if (result.Contains(ERROR_504))
                        {
                            Debug.LogWarning(ERROR_504);
                        }
                        else if (result.Contains(ERROR_502))
                        {
                            Debug.LogWarning(ERROR_502);
                        }
                        else if (result.Contains(ERROR_500))
                        {
                            Debug.LogWarning(ERROR_500);
                        }
                        else
                        {
                            Debug.LogWarning($"invalid response:\n{ result }");
                        }

                        //invalidate:
                        result = null;

                        await Task.Delay(1000);  //try again after delay
                    }
                    else if (result.StartsWith("{\"error\": \"Invalid JSON.\"}"))
                    {
                        //log warning:
                        Debug.LogWarning($"invalid JSON. Try decreasing { nameof(maxCoordinatesPerRequest) }");

                        await Task.Delay(1000);  //try again after delay
                    }
                }
                catch (System.Net.WebException ex)
                {
                    Debug.LogException(ex);
                    await Task.Delay(1000);  //try again after delay
                }
                catch (System.Exception ex)
                {
                    Debug.LogException(ex);
                    await Task.Delay(1000);  //try again after delay
                }
            }

            //log response:
            if (logTraffic)
            {
                Debug.Log($"\tresponse:\n{ result }");
            }

            //return results:
            return(result);
        }
        public async Task GetElevationData
        (
            string filePath,
            IElevationServiceProvider serviceProvider,
            string apikey,
            Ticket <float> ticket,
            Coordinate start,
            Coordinate end,
            CoordinateInt resolution,
            int maxCoordinatesPerRequest,
            System.Action repaintWindowCallback,
            System.Action onFinish,
            bool logTraffic
        )
        {
            Debug.Log($"{ nameof(GetElevationData) }() started");

            IO.FileStream   stream = null;
            IO.StreamWriter writer = null;

            try
            {
                int skip;

                //open file stream:
                {
                    IO.FileMode fileMode;
                    if (
                        IO.File.Exists(filePath) &&
                        EditorUtility.DisplayDialog(
                            "Decide",
                            "CONTINUE: Setting must match to succeessfully continue\nOVERWRITE: data will be lost",
                            "CONTINUE",
                            "OVERWRITE"
                            )
                        )
                    {
                        fileMode = IO.FileMode.Append;
                        skip     = 0;
                        ForEachLine(
                            filePath,
                            (line) => skip++
                            );
                    }
                    else
                    {
                        fileMode = IO.FileMode.Create;
                        skip     = 0;
                    }

                    stream = new IO.FileStream(
                        filePath,
                        fileMode,
                        IO.FileAccess.Write,
                        IO.FileShare.Read,
                        4096,
                        IO.FileOptions.SequentialScan
                        );
                    writer = new IO.StreamWriter(stream);
                }

                //populate stack:
                Stack <Coordinate> coordinates = new Stack <Coordinate>();
                {
                    //NOTE: i bet this wont work for ranges overlaping -180/180 latitude crossline etc
                    Coordinate origin = new Coordinate {
                        longitude = Mathf.Min(start.longitude, end.longitude),
                        latitude  = Mathf.Min(start.latitude, end.latitude)
                    };

                    float stepX = Mathf.Abs(end.longitude - start.longitude) / (float)resolution.longitude;
                    float stepY = Mathf.Abs(end.latitude - start.latitude) / (float)resolution.latitude;
                    for (int Y = 0; Y < resolution.latitude; Y++)
                    {
                        for (int X = 0; X < resolution.longitude; X++)
                        {
                            coordinates.Push(
                                new Coordinate {
                                latitude  = origin.latitude + (float)Y * stepY,
                                longitude = origin.longitude + (float)X * stepX
                            }
                                );
                        }
                    }
                }
                int numStartingCoordinates = coordinates.Count;

                //skip entries (if applies):
                if (skip != 0)
                {
                    Debug.Log($"Skipping { skip } entries");
                    for (int i = 0; i < skip; i++)
                    {
                        coordinates.Pop();
                    }
                }

                //process stack in batches:
                List <float> elevations = new List <float>(maxCoordinatesPerRequest);
                while (coordinates.Count != 0)
                {
                    //call api:
                    {
                        //get response:
                        string response = await HttpRequest(
                            serviceProvider :            serviceProvider,
                            coordinates :                coordinates,
                            apikey :                     apikey,
                            maxCoordinatesPerRequest :   maxCoordinatesPerRequest,
                            ticket :                     ticket,
                            logTraffic :                 logTraffic
                            );

                        //
                        if (serviceProvider.ParseResponse(response, elevations))
                        {
                            if (elevations.Count != 0)
                            {
                                //write entries to file:
                                foreach (float elevation in elevations)
                                {
                                    writer.WriteLine(elevation);
                                }
                                elevations.Clear();
                            }
                            else
                            {
                                throw new System.Exception("Parsed response contains no entries");
                            }
                        }
                        else
                        {
                            throw new System.Exception("Parse failed");
                        }
                    }

                    //
                    ticket.value = 1f - ((float)coordinates.Count / (float)numStartingCoordinates);
                    repaintWindowCallback();

                    //test for abort:
                    if (ticket.invalid)
                    {
                        repaintWindowCallback();
                        throw new System.Exception("aborted");
                    }
                }

                //task done:
                ticket.value = 1f;
                repaintWindowCallback();
                Debug.Log(coordinates.Count == 0 ? "SUCCESS!" : $"ERROR, unprocessed coordinates: { coordinates.Count } )");
                await Task.CompletedTask;
            }
            catch (System.Exception ex) { Debug.LogException(ex); }
            finally
            {
                //log:
                Debug.Log($"{ nameof(GetElevationData) }() finished");

                //close streams:
                if (writer != null)
                {
                    writer.Close();
                }
                if (stream != null)
                {
                    stream.Close();
                }

                //call on finish:
                if (onFinish != null)
                {
                    onFinish();
                }
            }
        }