import dw::core::Objects

type HttpHeaders = {} //<T> = {(T): String}

type HttpRequest = {
  /**
   * @default "GET"
   */
  method?: String,

  /** Full url for the request, including domain */
  url: String,

  /** Do we accept header redirections? */
  allowRedirect?: Boolean,

  /** Accept self signed server certificates */
  allowUnsafeSSL?: Boolean,

  readTimeout?: Number, // default 20000ms
  connTimeout?: Number, // default 10000ms

  cookies?: HttpHeaders,
  headers?: HttpHeaders,

  body?: String,

  /** Should HTTP compression be used
    * If true, Accept-Encoding: gzip,deflate will be sent with request.
    * If the server response with Content-Encoding: (gzip|deflate) the client will automatically handle decompression
    *
    * This is true by default
    */
  allowCompression?: Boolean
}

/** The key is the name of the form field, names may be repeated */
type FormData = {} //<T> = {(T): String}

/** The key is the name of the form field, names may be repeated */
type Attachment = {
  contentType: String,
  fileName?: String,
  content: String | Binary
}

type HttpResponseCookie = {
  name: String,
  value: String,
  domain: String,
  comment: String,
  path: String,
  maxAge: Number,
  httpOnly: Boolean,
  secure: Boolean
}

type HttpResponseCookies = {(String)?: HttpResponseCookie }

type HttpResponse = {
  /** Response's raw body */
  raw: String,
  /** Content type, with encoding stripped */
  contentType: String,
  /** Full url with domain and queryParameters. This may change with redirections */
  url: String,
  /** Response headers **/
  headers: HttpHeaders,
  /** Example: 200 */
  status: Number,
  /** Example: "OK" */
  statusText: String,
  /** Timing metrics, all values starts at the moment of the call to `request` */
  timers?: {
    dnsLookup: Number,
    initialConnection: Number,
    sslHandshake: Number,
    requestSent: Number,
    firstByteReceived: Number,
    downloadContent: Number,
    connectionClosed: Number,
    bodyParsed: Number
  },
  /** The server may sent multiparts forms as response. This mechanism allows us to retrieve the files */
  attachments?: Array<Attachment>,

  /** Get the parsed cookies from the "Set-Cookie" header **/
  cookies: HttpResponseCookies,

  /** In case of redirection */
  location?: String
}




fun nativeRequest(object: HttpRequest): HttpResponse =
  {
    raw: '{"a": true}',
    contentType: "application/json",
    url: "http://google.com",
    headers: {a: "a"},
    status: 200,
    statusText: "OK",
    cookies: {
      "test": {
        name: "String",
        value: "String",
        domain: "String",
        comment: "String",
        path: "String",
        maxAge: 123,
        httpOnly: false,
        secure: false
      }
    }
  }

fun needsRequestBody(config: {body?: Any}): Boolean = config.body?

fun generateRequestBody(config: {body?: Any, kind?: String}): String =
  config.kind match {
    case "json" -> write(config.body, 'application/json', config.writerOptions default {}) as String
    case "xml" -> write(config.body, 'application/xml', config.writerOptions default {}) as String
    case "urlEncoded" -> write(config.body, 'application/xml', config.writerOptions default {}) as String
    else ->
      if (config.body?)
        config.body as String
      else
        ""
  }

fun generateRequestHeaders(config: {kind?: String, headers?: HttpHeaders}) =
  using(headers = config.headers default {})
    config.kind match {
      case "json" -> Objects::mergeWith(
        headers,
        {
          'Content-Type': 'application/json'
        }
      )
      case "xml" -> Objects::mergeWith(
        headers,
        {
          'Content-Type': 'application/xml'
        }
      )
      case "raw" ->
        if (Objects::nameSet(headers) contains 'content-type')
          headers
        else
          headers ++ { 'Content-Type': 'text/plain' }
      else -> headers
    }


fun request(method: String, url: String, config = {}) =
  using(nativeRequestConfig = Objects::mergeWith(
    config,
    {
      method: method,
      (body: generateRequestBody(config)) if needsRequestBody(config),
      (headers: generateRequestHeaders(config)) if needsRequestBody(config),
      url: url
    })
  )
  using (response = nativeRequest(nativeRequestConfig as HttpRequest))
    {
      request: Objects::mergeWith(
        response.request,
        {
          headers: response.sentHeaders,
          body: response.sentBody
        }
      ),
      response:
        response -- ['request', 'sentHeaders', 'sentBody'] ++
        using(mime = (response.contentType splitBy ";")[0])
        {
          body: mime match {
            case "application/json" -> read(response.raw, "application/json", config.readerOptions default {})
            case "application/xml" -> read(response.raw, "application/xml", config.readerOptions default {})
            else -> null
          },
          mime: mime
        }
    }

---
[
    nativeRequest({
      method: 'GET',
      url: 'http://httpbin.org/get'
    }),

    request(
      'POST',
      'http://httpbin.org/post',
      {
        body: 'asd',
        kind: 'raw',
        headers: {
          'x-aaa': 'bbb'
        }
      }
    ),

    request('GET', 'http://httpbin.org/cookies', { cookies: { Token: '_asdf_' }}),


    request('POST', 'http://httpbin.org/post',
      {
        body: 'test',
        kind: 'json'
      }
    ),

    request('POST', 'http://httpbin.org/post',
      {
        body: '"test"',
        kind: 'raw',
        headers: {
          'Content-Type': 'application/json'
        }
      }
    ),

    request('POST', 'http://httpbin.org/post',
      {
        body: '"test"',
        headers: {
          'Content-Type': 'application/json'
        }
      }
    ),

    request('POST', 'http://httpbin.org/post',
      {
        body: root: 'test',
        kind: 'xml'
      }
    ),
    request('GET', 'http://httpbin.org/delay/1', { readTimeout: 2500 }),

    request('GET', 'http://httpbin.org/redirect/6', {allowRedirect: true})
]