content: package com.annimon.ownlang.modules.http;
  
  import com.annimon.ownlang.lib.*;
  import java.io.IOException;
  import java.io.UnsupportedEncodingException;
  import java.util.List;
  import java.util.Map;
  import okhttp3.FormBody;
  import okhttp3.MediaType;
  import okhttp3.OkHttpClient;
  import okhttp3.Request;
  import okhttp3.RequestBody;
  import okhttp3.Response;
  import okhttp3.internal.http.HttpMethod;
  
  public final class HttpFunctions {
      
      private static final Value
              HEADER_KEY = new StringValue("header"),
              CHARSET_KEY = new StringValue("charset"),
              ENCODED_KEY = new StringValue("encoded"),
              CONTENT_TYPE = new StringValue("content_type"),
              EXTENDED_RESULT = new StringValue("extended_result");
      
      private static final MediaType URLENCODED_MEDIA_TYPE = MediaType.parse("application/x-www-form-urlencoded");
      
      private final OkHttpClient client = new OkHttpClient();
  
      public Value httpSync(Value[] args) {
          Arguments.checkRange(1, 4, args.length);
  
          String url = args[0].asString();
          String method = (args.length >= 2) ? args[1].asString() : "GET";
          Value requestParams = (args.length >= 3) ? args[2] : MapValue.EMPTY;
          MapValue options = (args.length >= 4) ? ValueUtils.consumeMap(args[3], 3) : MapValue.EMPTY;
  
          boolean isSuccessful;
          Value result = options.containsKey(EXTENDED_RESULT) ? MapValue.EMPTY : StringValue.EMPTY;
          try (Response response = executeRequest(url, method, requestParams, options)) {
              isSuccessful = response.isSuccessful();
              if (isSuccessful) {
                  result = getResult(response, options);
              }
          } catch (IOException ioe) {
              isSuccessful = false;
          }
          return new ArrayValue(new Value[] {
                  NumberValue.fromBoolean(isSuccessful),
                  result
          });
      }
  
      public Value http(Value[] args) {
          Arguments.checkRange(1, 5, args.length);
  
          String url = args[0].asString();
          String method = "GET";
          Value requestParams = MapValue.EMPTY;
          MapValue options = MapValue.EMPTY;
          Function callback = FunctionValue.EMPTY.getValue();
  
          switch (args.length) {
              case 1: // http(url)
                  break;
  
              case 2: // http(url, method) || http(url, callback)
                  if (args[1].type() == Types.FUNCTION) {
                      callback = ValueUtils.consumeFunction(args[1], 1);
                  } else {
                      method = args[1].asString();
                  }
                  break;
  
              case 3: // http(url, method, params) || http(url, method, callback)
                  method = args[1].asString();
                  if (args[2].type() == Types.FUNCTION) {
                      callback = ValueUtils.consumeFunction(args[2], 2);
                  } else {
                      requestParams = args[2];
                  }
                  break;
  
              case 4: // http(url, method, params, callback)
                  method = args[1].asString();
                  requestParams = args[2];
                  callback = ValueUtils.consumeFunction(args[3], 3);
                  break;
  
              case 5: // http(url, method, params, headerParams, callback)
                  method = args[1].asString();
                  requestParams = args[2];
                  options = ValueUtils.consumeMap(args[3], 3);
                  callback = ValueUtils.consumeFunction(args[4], 4);
                  break;
          }
  
          try {
              final Response response = executeRequest(url, method, requestParams, options);
              callback.execute(getResult(response, options));
              return NumberValue.fromBoolean(response.isSuccessful());
          } catch (IOException ex) {
              return NumberValue.fromBoolean(false);
          }
      }
  
      private Response executeRequest(String url, String methodStr, Value requestParams, MapValue options) throws IOException {
          final String method = methodStr.toUpperCase();
          final Request.Builder builder = new Request.Builder()
                  .url(url)
                  .method(method, getRequestBody(method, requestParams, options));
          if (options.containsKey(HEADER_KEY)) {
              applyHeaderParams((MapValue) options.get(HEADER_KEY), builder);
          }
          return client.newCall(builder.build()).execute();
      }
  
      private Value getResult(Response response, MapValue options) throws IOException {
          if (options.containsKey(EXTENDED_RESULT)) {
              final MapValue map = new MapValue(10);
              map.set("text", new StringValue(response.body().string()));
              map.set("message", new StringValue(response.message()));
              map.set("code", NumberValue.of(response.code()));
              final MapValue headers = new MapValue(response.headers().size());
              for (Map.Entry<String, List<String>> entry : response.headers().toMultimap().entrySet()) {
                  final int valuesSize = entry.getValue().size();
                  final ArrayValue values = new ArrayValue(valuesSize);
                  for (int i = 0; i < valuesSize; i++) {
                      values.set(i, new StringValue(entry.getValue().get(i)));
                  }
                  headers.set(entry.getKey(), values);
              }
              map.set("headers", headers);
              map.set("content_length", NumberValue.of(response.body().contentLength()));
              map.set(CONTENT_TYPE, new StringValue(response.body().contentType().toString()));
              return map;
          }
          return new StringValue(response.body().string());
      }
      
      private void applyHeaderParams(MapValue headerParams, Request.Builder builder) {
          for (Map.Entry<Value, Value> p : headerParams) {
              builder.header(p.getKey().asString(), p.getValue().asString());
          }
      }
      
      private RequestBody getRequestBody(String method, Value params, MapValue options) throws UnsupportedEncodingException {
          if (!HttpMethod.permitsRequestBody(method)) return null;
          
          if (params.type() == Types.MAP) {
              return getMapRequestBody((MapValue) params, options);
          }
          return getStringRequestBody(params, options);
      }
      
      private RequestBody getMapRequestBody(MapValue params, MapValue options) {
          final FormBody.Builder form = new FormBody.Builder();
          final boolean alreadyEncoded = (options.containsKey(ENCODED_KEY)
                  && options.get(ENCODED_KEY).asInt() != 0);
          for (Map.Entry<Value, Value> param : params) {
              final String name = param.getKey().asString();
              final String value = param.getValue().asString();
              if (alreadyEncoded)
                  form.addEncoded(name, value);
              else 
                  form.add(name, value);
          }
          return form.build();
      }
  
      private RequestBody getStringRequestBody(Value params, MapValue options) throws UnsupportedEncodingException {
          final MediaType type;
          if (options.containsKey(CONTENT_TYPE)) {
              type = MediaType.parse(options.get(CONTENT_TYPE).asString());
          } else {
              type = URLENCODED_MEDIA_TYPE;
          }
          
          if (options.containsKey(CHARSET_KEY)) {
              final String charset = options.get(CHARSET_KEY).asString();
              return RequestBody.create(params.asString().getBytes(charset), type);
          }
          
          return RequestBody.create(params.asString(), type);
      }
  }
  