import Dispatcher;

namespace Http {

    enum RequestMode {
        TEXT = 0,
        BINARY = 1,
    }

    class HttpRequest {

        /*
            nativeData[0] -> a reference to an ongoing HTTP request object
            nativeData[1] -> if applicable, a mutex for accessing the asynchronous HTTP request
            nativeData[2] -> boolean: is done
        */
        static field _active_requests = null;

        field _url;
        field _method = Http.Method.GET;
        field _headers = [];
        field _content = null;
        field _mode = Http.RequestMode.TEXT;
        field _isReady = false;
        field _contentIsBinary = false;
        field _userAgent = Http.DEFAULT_USER_AGENT;
        field _contentType = null;
        field _timesPolled = 0;

        constructor(url) {
            this._url = url + '';
        }

        function setMethod(method) {
            this._method = method;
            return this;
        }

        function setHeader(name, value) {
            Core.assert(Core.typeof(name) == Core.Type.STRING, "HTTP header name must be a string.");
            name = name.trim();
            Core.assert(name.length > 0, "HTTP header name must be non-empty.");
            Core.assert(value != null, "HTTP header value cannot be null.");

            value = value + '';
            switch (name.lower()) {
                case 'user-agent': this._userAgent = value; break;
                case 'content-type': this._contentType = value; break;
                default: this._headers.add(name, value); break;
            }
            return this;
        }

        function setContentBytes(value, contentType = null) {
            obj = new Object();
            if (!$lib_http_fastEnsureAllBytes(value, obj)) Core.fail("Input must be a list of byte values");

            this._content = obj;
            this._contentIsBinary = true;
            return this._setContentHelper(contentType);
        }

        function setContent(value, contentType = null) {
            this._content = value + '';
            this._contentIsBinary = false;
            return this._setContentHelper(contentType);
        }

        function _setContentHelper(contentType) {
            if (contentType != null) {
                this.setHeader('Content-Type', contentType);
            }
            if (this._method == null) {
                this._method = Http.Method.POST;
            }
            return this;
        }

        function setMode(mode) {
            this._mode = mode;
            return this;
        }

        function send() {
            this._sendImpl(false, null);
            this._isReady = true;
            return this.getResponse();
        }

        function sendAsync(cb) {
            return this._sendImpl(true, cb);
        }

        function _sendImpl(isAsync, cb) {

            method = {
                Http.Method.GET: 'GET',
                Http.Method.POST: 'POST',
                Http.Method.PUT: 'PUT',
                Http.Method.DELETE: 'DELETE',
            }.get(this._method, this._method) + '';

            this._headers.add('User-Agent', this._userAgent);
            if (this._contentType != null) {
                this._headers.add('Content-Type', this._contentType);
            }

            wrappedCb = () => {
                this._isReady = true;
                res = this.getResponse();
                cb(res);
                Dispatcher.DispatcherInternal._notifyCbCompletedToFramework();
            };

            $lib_http_sendRequest(
                this,
                isAsync,
                method,
                this._url,
                this._headers,
                this._content,
                this._mode,
                Core.getCurrentExecutionToken(),
                wrappedCb,
                Dispatcher.DispatcherInternal._nativeQueue);
            return this;
        }

        function isDone() {
            if (this._isReady) return true;

            if (this._timesPolled++ > 10) {
                this._timesPolled = 0;
                // Yield the thread for a tiny moment to give the handler a chance to run for platforms that require
                // asynchronous HTTP requests to invoke a completion handler on the main thread.
                Core.sleep(0.001);
            }

            if ($lib_http_pollRequest(this)) { // updates the native data
                this._isReady = true;
            }

            return this._isReady;
        }

        function getResponse() {
            Core.assert(this._isReady, "Request not finished.");
            return Http.HttpResponse._initialize(this);
        }
    }
}
