#include "response.h"

#include "log.h"
#include "meinheld.h"
#include "util.h"

#define CRLF "\r\n"
#define DELIM ": "

#define H_MSG_500                                                             \
  "HTTP/1.0 500 Internal Server Error\r\nContent-Type: text/html\r\nServer: " \
  " " SERVER "\r\n\r\n"

#define H_MSG_503                                      \
  "HTTP/1.0 503 Service Unavailable\r\nContent-Type: " \
  "text/html\r\nServer: " SERVER "\r\n\r\n"

#define H_MSG_400                                                          \
  "HTTP/1.0 400 Bad Request\r\nContent-Type: text/html\r\nServer: " SERVER \
  "\r\n\r\n"

#define H_MSG_408                                                              \
  "HTTP/1.0 408 Request Timeout\r\nContent-Type: text/html\r\nServer: " SERVER \
  "\r\n\r\n"

#define H_MSG_411                                                              \
  "HTTP/1.0 411 Length Required\r\nContent-Type: text/html\r\nServer: " SERVER \
  "\r\n\r\n"

#define H_MSG_413                                           \
  "HTTP/1.0 413 Request Entity Too Large\r\nContent-Type: " \
  "text/html\r\nServer: " SERVER "\r\n\r\n"

#define H_MSG_417                                     \
  "HTTP/1.1 417 Expectation Failed\r\nContent-Type: " \
  "text/html\r\nServer: " SERVER "\r\n\r\n"

#define MSG_500                                                                \
  H_MSG_500                                                                    \
      "<html><head><title>500 Internal Server "                                \
      "Error</title></head><body><h1>Internal Server Error</h1><p>The server " \
      "encountered an internal error and was unable to complete your "         \
      "request.  Either the server is overloaded or there is an error in the " \
      "application.</p></body></html>"

#define MSG_503                                                                \
  H_MSG_503                                                                    \
      "<html><head><title>Service Unavailable</title></head><body><p>Service " \
      "Unavailable.</p></body></html>"

#define MSG_400                                                    \
  H_MSG_400                                                        \
      "<html><head><title>Bad Request</title></head><body><p>Bad " \
      "Request.</p></body></html>"

#define MSG_408                                                            \
  H_MSG_408                                                                \
      "<html><head><title>Request Timeout</title></head><body><p>Request " \
      "Timeout.</p></body></html>"

#define MSG_411                                                           \
  H_MSG_411                                                               \
      "<html><head><title>Length Required</title></head><body><p>Length " \
      "Required.</p></body></html>"

#define MSG_413                                          \
  H_MSG_413                                              \
      "<html><head><title>Request Entity Too "           \
      "Large</title></head><body><p>Request Entity Too " \
      "Large.</p></body></html>"

#define MSG_417                         \
  H_MSG_417                             \
      "<html><head><title>Expectation " \
      "Failed</title></head><body><p>Expectation Failed.</p></body></html>"

ResponseObject *start_response = NULL;

static PyObject *wsgi_to_bytes(PyObject *value) {
  PyObject *result = NULL;

#ifdef PY3
  if (!PyUnicode_Check(value)) {
    PyErr_Format(PyExc_TypeError,
                 "expected unicode object, value "
                 "of type %.200s found",
                 value->ob_type->tp_name);
    return NULL;
  }

  result = PyUnicode_AsLatin1String(value);

  if (!result) {
    PyErr_SetString(PyExc_ValueError,
                    "unicode object contains non "
                    "latin-1 characters");
    return NULL;
  }
#else
  if (!PyBytes_Check(value)) {
    PyErr_Format(PyExc_TypeError,
                 "expected byte string object, "
                 "value of type %.200s found",
                 value->ob_type->tp_name);
    return NULL;
  }

  Py_INCREF(value);
  result = value;
#endif

  return result;
}

static int blocking_write(client_t *client, char *data, size_t len) {
  size_t r = 0, send_len = len;
  while ((int)len > 0) {
    if (len < send_len) {
      send_len = len;
    }
    Py_BEGIN_ALLOW_THREADS r = write(client->fd, data, send_len);
    Py_END_ALLOW_THREADS switch (r) {
      case 0:
        return 1;
        break;
      case -1:
        if (errno == EAGAIN || errno == EWOULDBLOCK) { /* try again later */
          usleep(200);
          break;
        } else {
          // fatal error
          // close
          if (errno == EPIPE) {
            // Connection reset by peer
            client->keep_alive = 0;
            client->status_code = 500;
            client->header_done = 1;
            client->response_closed = 1;
          } else {
            PyErr_SetFromErrno(PyExc_IOError);
            /* write_error_log(__FILE__, __LINE__); */
            call_error_logger();
            client->keep_alive = 0;
          }
          return -1;
        }
      default:
        data += (int)r;
        len -= r;
        client->write_bytes += r;
    }
  }
  return 1;
}

void send_error_page(client_t *client) {
  shutdown(client->fd, SHUT_RD);
  if (client->header_done || client->response_closed) {
    // already sended response data
    // close connection
    return;
  }

  /* int status = client->bad_request_code; */
  /* int r = status < 0 ? status * -1:status; */
  /* client->status_code = client->bad_request_code; */

  DEBUG("send_error_page status_code %d client %p", client->status_code,
        client);

  switch (client->status_code) {
    case 400:
      blocking_write(client, MSG_400, sizeof(MSG_400) - 1);
      client->write_bytes -= sizeof(H_MSG_400) - 1;
      break;
    case 408:
      blocking_write(client, MSG_408, sizeof(MSG_408) - 1);
      client->write_bytes -= sizeof(H_MSG_408) - 1;
      break;
    case 411:
      blocking_write(client, MSG_411, sizeof(MSG_411) - 1);
      client->write_bytes -= sizeof(H_MSG_411) - 1;
      break;
    case 413:
      blocking_write(client, MSG_413, sizeof(MSG_413) - 1);
      client->write_bytes -= sizeof(H_MSG_413) - 1;
      break;
    case 417:
      blocking_write(client, MSG_417, sizeof(MSG_417) - 1);
      client->write_bytes -= sizeof(H_MSG_417) - 1;
      break;
    case 503:
      blocking_write(client, MSG_503, sizeof(MSG_503) - 1);
      client->write_bytes -= sizeof(H_MSG_503) - 1;
      break;
    default:
      // Internal Server Error
      blocking_write(client, MSG_500, sizeof(MSG_500) - 1);
      client->write_bytes -= sizeof(H_MSG_500) - 1;
      break;
  }
  client->keep_alive = 0;
  client->header_done = 1;
  client->response_closed = 1;
}

static write_bucket *new_write_bucket(int fd, int cnt) {
  write_bucket *bucket;
  iovec_t *iov;

  bucket = PyMem_Malloc(sizeof(write_bucket));
  if (bucket == NULL) {
    return NULL;
  }
  memset(bucket, 0, sizeof(write_bucket));

  bucket->fd = fd;
  iov = (iovec_t *)PyMem_Malloc(sizeof(iovec_t) * cnt);
  if (iov == NULL) {
    PyMem_Free(bucket);
    return NULL;
  }
  memset(iov, 0, sizeof(iovec_t));
  bucket->iov = iov;
  bucket->iov_size = cnt;
  GDEBUG("allocate %p", bucket);
  return bucket;
}

static void free_write_bucket(write_bucket *bucket) {
  GDEBUG("free %p", bucket);
  Py_CLEAR(bucket->temp1);
  Py_CLEAR(bucket->chunk_data);
  PyMem_Free(bucket->iov);
  PyMem_Free(bucket);
}

static void set2bucket(write_bucket *bucket, char *buf, size_t len) {
  bucket->iov[bucket->iov_cnt].iov_base = buf;
  bucket->iov[bucket->iov_cnt].iov_len = len;
  bucket->iov_cnt++;
  bucket->total += len;
  bucket->total_size += len;
}

static void set_chunked_data(write_bucket *bucket, char *lendata, size_t lenlen,
                             char *data, size_t datalen) {
  set2bucket(bucket, lendata, lenlen);
  set2bucket(bucket, CRLF, 2);
  set2bucket(bucket, data, datalen);
  set2bucket(bucket, CRLF, 2);
}

static void set_last_chunked_data(write_bucket *bucket) {
  set2bucket(bucket, "0", 1);
  set2bucket(bucket, CRLF, 2);
  set2bucket(bucket, CRLF, 2);
}

static void add_header(write_bucket *bucket, char *key, size_t keylen,
                       char *val, size_t vallen) {
  set2bucket(bucket, key, keylen);
  set2bucket(bucket, DELIM, 2);
  set2bucket(bucket, val, vallen);
  set2bucket(bucket, CRLF, 2);
}

#ifdef DEVELOP
static void writev_log(write_bucket *data) {
  int i = 0;
  char *c;
  size_t len;
  for (; i < data->iov_cnt; i++) {
    c = data->iov[i].iov_base;
    len = data->iov[i].iov_len;
    printf("%.*s", (int)len, c);
  }
}
#endif

static response_status writev_bucket(write_bucket *data) {
  size_t w;
  int i = 0;
  Py_BEGIN_ALLOW_THREADS
#ifdef DEVELOP
      BDEBUG("\nwritev_bucket fd:%d", data->fd);
  printf("\x1B[34m");
  writev_log(data);
  printf("\x1B[0m\n");
#endif
  w = writev(data->fd, data->iov, data->iov_cnt);
  BDEBUG("writev fd:%d ret:%d total_size:%d", data->fd, (int)w, data->total);
  Py_END_ALLOW_THREADS if (w == -1) {
    // error
    if (errno == EAGAIN || errno == EWOULDBLOCK) {
      BDEBUG("try again later");
      return STATUS_SUSPEND;
    } else {
      // ERROR
      PyErr_SetFromErrno(PyExc_IOError);
      /* write_error_log(__FILE__, __LINE__); */
      call_error_logger();
      return STATUS_ERROR;
    }
  }
  if (w == 0) {
    data->sended = 1;
    return STATUS_OK;
  } else {
    if (data->total > w) {
      for (; i < data->iov_cnt; i++) {
        if (w > data->iov[i].iov_len) {
          // already write
          w -= data->iov[i].iov_len;
          data->iov[i].iov_len = 0;
        } else {
          data->iov[i].iov_base += w;
          data->iov[i].iov_len = data->iov[i].iov_len - w;
          break;
        }
      }
      data->total = data->total - w;
      BDEBUG("writev_bucket write %d progress %d/%d", (int)w, data->total,
             data->total_size);
      // resume
      // again later
      return writev_bucket(data);
    }
    data->sended = 1;
  }
  data->sended = 1;
  return STATUS_OK;
}

static int set_file_content_length(client_t *client, write_bucket *bucket) {
  struct stat info;
  int in_fd, ret = 0;
  size_t size = 0;
  Py_ssize_t valuelen = 0;
  FileWrapperObject *filewrap = NULL;
  PyObject *filelike = NULL, *length = NULL;
  char *value = NULL;

  filewrap = (FileWrapperObject *)client->response;
  filelike = filewrap->filelike;

  in_fd = PyObject_AsFileDescriptor(filelike);
  if (in_fd == -1) {
    call_error_logger();
    return -1;
  }
  if (fstat(in_fd, &info) == -1) {
    PyErr_SetFromErrno(PyExc_IOError);
    return -1;
  }

  size = info.st_size;
  client->content_length_set = 1;
  client->content_length = size;
  DEBUG("set content length:%" PRIu64, size);
  length = PyBytes_FromFormat("%zu", size);
  if (length == NULL) {
    return -1;
  }

  PyBytes_AsStringAndSize(length, &value, &valuelen);

  add_header(bucket, "Content-Length", 14, value, valuelen);
  ret = PyList_Append(bucket->temp1, length);
  if (ret == -1) {
    return -1;
  }
  Py_DECREF(length);
  return 1;
}

/*
static int
get_len(PyObject *v)
{
    Py_ssize_t res;
    res = PyObject_Size(v);
    if (res < 0 && PyErr_Occurred()){
        PyErr_Clear();
        return 0;
    }
    return (int)res;
}

static void
set_content_length(client_t *client, write_bucket *bucket, char *data, size_t
datalen )
{
    PyObject *header, *length;
    char *value;
    Py_ssize_t valuelen;

    if(client->headers && !client->content_length_set){
        if (get_len(client->response) == 1) {
            client->content_length_set = 1;
            DEBUG("set content_length %d", (int)datalen);
            //length = PyBytes_FromFormat("%zu", datalen);

            header = Py_BuildValue("(sO)", "Content-Length", length);
            Py_DECREF(length);

            PyList_Append(client->headers, header);
            Py_DECREF(header);
            PyBytes_AsStringAndSize(length, &value, &valuelen);
            add_header(bucket, "Content-Length", 14, value, valuelen);
        }
    }
}
*/

static int add_all_headers(write_bucket *bucket, PyObject *fast_headers,
                           int hlen, client_t *client) {
  int i;
  PyObject *tuple = NULL;
  PyObject *obj1 = NULL, *obj2 = NULL;
  PyObject *bytes1 = NULL, *bytes2 = NULL;
  char *name = NULL, *value = NULL;
  Py_ssize_t namelen, valuelen;
#ifdef PY3
  int o;
#endif

  if (likely(fast_headers != NULL)) {
    for (i = 0; i < hlen; i++) {
      tuple = PySequence_Fast_GET_ITEM(fast_headers, i);

      if (unlikely(!PyTuple_Check(tuple))) {
        PyErr_Format(PyExc_TypeError,
                     "list of tuple values "
                     "expected, value of type %.200s found",
                     tuple->ob_type->tp_name);
        goto error;
      }

      if (unlikely(PyTuple_GET_SIZE(tuple) != 2)) {
        PyErr_Format(PyExc_ValueError,
                     "tuple of length 2 "
                     "expected, length is %d",
                     (int)PyTuple_Size(tuple));
        goto error;
      }

      obj1 = PyTuple_GET_ITEM(tuple, 0);
      obj2 = PyTuple_GET_ITEM(tuple, 1);
      if (unlikely(!obj1)) {
        goto error;
      }
      if (unlikely(!obj2)) {
        goto error;
      }
      bytes1 = wsgi_to_bytes(obj1);
      if (unlikely(PyBytes_AsStringAndSize(bytes1, &name, &namelen) == -1)) {
        goto error;
      }

      // value
      bytes2 = wsgi_to_bytes(obj2);
      if (unlikely(PyBytes_AsStringAndSize(bytes2, &value, &valuelen) == -1)) {
        goto error;
      }

      if (unlikely(strchr(name, ':') != 0)) {
        PyErr_Format(PyExc_ValueError,
                     "header name may not contains ':'"
                     "response header with name '%s' and value '%s'",
                     name, value);
        goto error;
      }

      if (unlikely(strchr(name, '\n') != 0 || strchr(value, '\n') != 0)) {
        PyErr_Format(PyExc_ValueError,
                     "embedded newline in "
                     "response header with name '%s' and value '%s'",
                     name, value);
        goto error;
      }

      if (!strcasecmp(name, "Server") || !strcasecmp(name, "Date")) {
        continue;
      }

      if (client->content_length_set != 1 &&
          !strcasecmp(name, "Content-Length")) {
        char *v = value;
        long l = 0;

        errno = 0;
        l = strtol(v, &v, 10);
        if (*v || errno == ERANGE || l < 0) {
          PyErr_SetString(PyExc_ValueError, "invalid content length");
          goto error;
        }

        client->content_length_set = 1;
        client->content_length = l;
      }
      DEBUG("response header %s:%d : %s:%d", name, (int)namelen, value,
            (int)valuelen);
      add_header(bucket, name, namelen, value, valuelen);
#ifdef PY3
      // keep bytes string
      o = PyList_Append(bucket->temp1, bytes1);
      if (o != -1) {
        o = PyList_Append(bucket->temp1, bytes2);
        if (o == -1) {
          goto error;
        }
      } else {
        goto error;
      }
#endif
      Py_XDECREF(bytes1);
      Py_XDECREF(bytes2);
    }

  } else {
    RDEBUG("WARN header is lost...");
  }

  return 1;
error:
  if (PyErr_Occurred()) {
    /* write_error_log(__FILE__, __LINE__); */
    call_error_logger();
  }
  Py_XDECREF(bytes1);
  Py_XDECREF(bytes2);
  return -1;
}

static int add_status_line(write_bucket *bucket, client_t *client) {
  PyObject *object;
  char *value = NULL;
  Py_ssize_t valuelen;

  object = client->http_status;
  // TODO ERROR CHECK
  if (object) {
    DEBUG("add_status_line client %p", client);
    PyBytes_AsStringAndSize(object, &value, &valuelen);

    // write status code
    set2bucket(bucket, value, valuelen);

    add_header(bucket, "Server", 6, SERVER, sizeof(SERVER) - 1);
    // cache_time_update();
    add_header(bucket, "Date", 4, (char *)http_time, 29);
  } else {
    DEBUG("missing status_line %p", client);
  }
  return 1;
}

static PyObject *get_chunk_data(size_t datalen) {
  char lendata[32];
  int i = 0;
  i = snprintf(lendata, 32, "%zx", datalen);
  DEBUG("Transfer-Encoding chunk_size %s", lendata);
  return PyBytes_FromStringAndSize(lendata, (Py_ssize_t)i);
}

static void set_first_body_data(client_t *client, char *data, size_t datalen) {
  write_bucket *bucket = client->bucket;
  if (data) {
    if (client->chunked_response) {
      char *lendata = NULL;
      Py_ssize_t len = 0;

      PyObject *chunk_data = get_chunk_data(datalen);
      // TODO CHECK ERROR
      PyBytes_AsStringAndSize(chunk_data, &lendata, &len);
      set_chunked_data(bucket, lendata, len, data, datalen);
      bucket->chunk_data = chunk_data;
    } else {
      set2bucket(bucket, data, datalen);
    }
  }
}

// Check if response body must not be sent.
// See https://tools.ietf.org/html/rfc7230#section-3.3.1
static int is_no_body(client_t *client) {
  if (client->current_req->method == HTTP_HEAD) {
    return 1;
  }
  return client->status_code < 200       // 1xx
         || client->status_code == 204   // No content
         || client->status_code == 304;  // Not Modified
}

static response_status write_headers(client_t *client, char *data,
                                     size_t datalen, char is_file) {
  write_bucket *bucket = 0;
  uint32_t hlen = 0;
  PyObject *headers = NULL, *templist = NULL;
  response_status ret;

  DEBUG("header write? %d", client->header_done);
  if (client->header_done) {
    return STATUS_OK;
  }

  // TODO ERROR CHECK
  if (!client->headers) {
    goto error;
  }
  headers = PySequence_Fast(client->headers, "header must be list");
  if (!headers) {
    goto error;
  }
  hlen = PySequence_Fast_GET_SIZE(headers);

  bucket = new_write_bucket(client->fd, (hlen * 4) + 42);
  if (bucket == NULL) {
    goto error;
  }
  templist = PyList_New(hlen * 4);
  if (templist == NULL) {
    goto error;
  }
  bucket->temp1 = templist;

  if (add_status_line(bucket, client) == -1) {
    goto error;
  }
  // write header
  if (add_all_headers(bucket, headers, hlen, client) == -1) {
    // Error
    goto error;
  }

  // check content_length_set
  if (!is_no_body(client) && data && !client->content_length_set &&
      client->http_parser->http_minor == 1) {
    // Transfer-Encoding chunked
    add_header(bucket, "Transfer-Encoding", 17, "chunked", 7);
    client->chunked_response = 1;
  }

  if (is_file && !client->content_length_set) {
    if (set_file_content_length(client, bucket) == -1) {
      goto error;
    }
  }

  if (client->status_code == 101) {
    add_header(bucket, "Connection", 10, "upgrade", 7);
  } else if (client->keep_alive == 1) {
    // Keep-Alive
    add_header(bucket, "Connection", 10, "Keep-Alive", 10);
  } else {
    add_header(bucket, "Connection", 10, "close", 5);
  }

  set2bucket(bucket, CRLF, 2);

  // write body
  client->bucket = bucket;
  set_first_body_data(client, data, datalen);

  ret = writev_bucket(bucket);
  if (ret != STATUS_SUSPEND) {
    client->header_done = 1;
    if (ret == STATUS_OK && data) {
      client->write_bytes += datalen;
    }
    // clear
    free_write_bucket(bucket);
    client->bucket = NULL;
  }

  Py_DECREF(headers);
  return ret;
error:
  if (PyErr_Occurred()) {
    /* write_error_log(__FILE__, __LINE__); */
    call_error_logger();
  }
  Py_XDECREF(headers);
  if (bucket) {
    free_write_bucket(bucket);
    client->bucket = NULL;
  }
  return STATUS_ERROR;
}

static int write_sendfile(int out_fd, int in_fd, int offset, size_t count) {
  int size = (int)count;
  int res;
#ifdef linux
  /*
  if (size == 0) {
      struct stat info;
      DEBUG("call fstat");
      if (fstat(in_fd, &info) == -1){
          PyErr_SetFromErrno(PyExc_IOError);
          write_error_log(__FILE__, __LINE__);
          return -1;
      }

      size = info.st_size - lseek(in_fd, 0, SEEK_CUR);
  }*/
  Py_BEGIN_ALLOW_THREADS res = sendfile(out_fd, in_fd, NULL, size);
  Py_END_ALLOW_THREADS return res;
#elif defined(__FreeBSD__)
  off_t len;
  Py_BEGIN_ALLOW_THREADS res =
      sendfile(in_fd, out_fd, offset, 0, NULL, &len, 0);
  Py_END_ALLOW_THREADS if (res == 0) { return len; }
  else {
    if (errno == EAGAIN || errno == EWOULDBLOCK) {
      return len;
    }
    return -1;
  }
#elif defined(__APPLE__)
  off_t len;
  Py_BEGIN_ALLOW_THREADS res = sendfile(in_fd, out_fd, offset, &len, NULL, 0);
  Py_END_ALLOW_THREADS if (res == 0) { return len; }
  else {
    if (errno == EAGAIN || errno == EWOULDBLOCK) {
      return len;
    }
    return -1;
  }
#endif
}

response_status close_response(client_t *client) {
  if (!client->response_closed) {
    // send all response
    // closing reponse object
    if (client->response && PyObject_HasAttrString(client->response, "close")) {
      PyObject *close = NULL;
      PyObject *args = NULL;
      PyObject *data = NULL;

      close = PyObject_GetAttrString(client->response, "close");

      args = PyTuple_New(0);
      data = PyEval_CallObject(close, args);
      DEBUG("call response object close");
      Py_DECREF(args);
      Py_XDECREF(data);
      Py_DECREF(close);
      client->response_closed = 1;
      if (PyErr_Occurred()) {
        return STATUS_ERROR;
      }
    }
  }
  return STATUS_OK;
}

static response_status process_sendfile(client_t *client) {
  PyObject *filelike = NULL;
  FileWrapperObject *filewrap = NULL;
  int in_fd, ret;

  filewrap = (FileWrapperObject *)client->response;
  filelike = filewrap->filelike;

  in_fd = PyObject_AsFileDescriptor(filelike);
  if (in_fd == -1) {
    PyErr_Clear();
    return STATUS_OK;
  }

  while (client->content_length > client->write_bytes) {
    ret = write_sendfile(client->fd, in_fd, client->write_bytes,
                         client->content_length);
    DEBUG("process_sendfile send %d", ret);
    switch (ret) {
      case 0:
        break;
      case -1:                                         /* error */
        if (errno == EAGAIN || errno == EWOULDBLOCK) { /* try again later */
          // next
          DEBUG("process_sendfile EAGAIN %d", ret);
          return STATUS_SUSPEND;
        } else { /* fatal error */
          client->keep_alive = 0;
          /* client->bad_request_code = 500; */
          client->status_code = 500;
          // close
          return STATUS_ERROR;
        }
      default:
        client->write_bytes += ret;
    }
  }
  // all send
  // disable_cork(client);
  return close_response(client);
}

static response_status process_write(client_t *client) {
  PyObject *iterator = NULL;
  PyObject *item, *chunk_data = NULL;
  char *buf = NULL, *lendata = NULL;
  Py_ssize_t buflen, len;
  write_bucket *bucket = NULL;
  response_status ret;

  DEBUG("process_write start");
  iterator = client->response_iter;
  if (iterator != NULL) {
    while ((item = PyIter_Next(iterator))) {
      if (PyBytes_Check(item)) {
        // TODO CHECK
        PyBytes_AsStringAndSize(item, &buf, &buflen);
        // write
        if (client->chunked_response) {
          bucket = new_write_bucket(client->fd, 4);
          if (bucket == NULL) {
            /* write_error_log(__FILE__, __LINE__); */
            call_error_logger();
            Py_DECREF(item);
            return STATUS_ERROR;
          }
          lendata = NULL;
          len = 0;

          chunk_data = get_chunk_data(buflen);
          // TODO CHECK ERROR
          PyBytes_AsStringAndSize(chunk_data, &lendata, &len);
          set_chunked_data(bucket, lendata, len, buf, buflen);
          bucket->chunk_data = chunk_data;
        } else {
          bucket = new_write_bucket(client->fd, 1);
          if (bucket == NULL) {
            /* write_error_log(__FILE__, __LINE__); */
            call_error_logger();
            Py_DECREF(item);
            return STATUS_ERROR;
          }
          set2bucket(bucket, buf, buflen);
        }
        bucket->temp1 = item;
        ret = writev_bucket(bucket);
        if (ret != STATUS_OK) {
          client->bucket = bucket;
          /* Py_DECREF(item); */
          return ret;
        }

        free_write_bucket(bucket);
        // mark
        client->write_bytes += buflen;
        // check write_bytes/content_length
        if (client->content_length_set) {
          if (client->content_length <= client->write_bytes) {
            // all done
            /* Py_DECREF(item); */
            break;
          }
        }
        /* Py_DECREF(item); */
      } else {
        PyErr_SetString(PyExc_TypeError, "response item must be a byte string");
        Py_DECREF(item);
        if (PyErr_Occurred()) {
          /* client->bad_request_code = 500; */
          client->status_code = 500;
          /* write_error_log(__FILE__, __LINE__); */
          call_error_logger();
          return STATUS_ERROR;
        }
      }
    }
    if (PyErr_Occurred()) {
      return STATUS_ERROR;
    }
    if (client->chunked_response) {
      DEBUG("write last chunk");
      // last packet
      bucket = new_write_bucket(client->fd, 3);
      if (bucket == NULL) {
        /* write_error_log(__FILE__, __LINE__); */
        call_error_logger();
        return STATUS_ERROR;
      }
      set_last_chunked_data(bucket);
      writev_bucket(bucket);
      free_write_bucket(bucket);
    }
    return close_response(client);
  }
  return STATUS_OK;
}

response_status process_body(client_t *client) {
  response_status ret;
  write_bucket *bucket;
  if (client->bucket) {
    bucket = (write_bucket *)client->bucket;
    // retry send
    ret = writev_bucket(bucket);

    if (ret == STATUS_OK) {
      client->write_bytes += bucket->total_size;
      free_write_bucket(bucket);
      client->bucket = NULL;
    } else if (ret == STATUS_ERROR) {
      free_write_bucket(bucket);
      client->bucket = NULL;
      return ret;
    } else {
      //
      return ret;
    }
  }

  if (CheckFileWrapper(client->response)) {
    ret = process_sendfile(client);
  } else {
    ret = process_write(client);
  }

  return ret;
}

static response_status start_response_file(client_t *client) {
  PyObject *filelike;
  FileWrapperObject *filewrap;
  int ret, in_fd, size;
  struct stat info;

  filewrap = (FileWrapperObject *)client->response;
  filelike = filewrap->filelike;

  in_fd = PyObject_AsFileDescriptor(filelike);
  if (in_fd == -1) {
    PyErr_Clear();
    DEBUG("can't get fd");
    return STATUS_ERROR;
  }
  ret = write_headers(client, NULL, 0, 1);
  if (!client->content_length_set) {
    if (fstat(in_fd, &info) == -1) {
      PyErr_SetFromErrno(PyExc_IOError);
      /* write_error_log(__FILE__, __LINE__);  */
      call_error_logger();
      return STATUS_ERROR;
    }

    size = info.st_size;
    client->content_length_set = 1;
    client->content_length = size;
  }
  return ret;
}

static response_status start_response_write(client_t *client) {
  PyObject *iterator;
  PyObject *item;
  char *buf;
  Py_ssize_t buflen;
  response_status ret;

  iterator = PyObject_GetIter(client->response);
  if (PyErr_Occurred()) {
    /* write_error_log(__FILE__, __LINE__); */
    call_error_logger();
    return STATUS_ERROR;
  }
  client->response_iter = iterator;

  item = PyIter_Next(iterator);
  DEBUG("client %p", client);
  if (item != NULL && PyBytes_Check(item)) {
    // write string only
    buf = PyBytes_AS_STRING(item);
    buflen = PyBytes_GET_SIZE(item);

    /* DEBUG("status_code %d body:%.*s", client->status_code, (int)buflen, buf);
     */
    ret = write_headers(client, buf, buflen, 0);
    // TODO when ret == STATUS_SUSPEND keep item
    Py_DECREF(item);
    return ret;
  } else {
    if (item == NULL && !PyErr_Occurred()) {
      // Stop Iteration
      RDEBUG("WARN iter item == NULL");
      return write_headers(client, NULL, 0, 0);
    } else {
      PyErr_SetString(PyExc_TypeError, "response item must be a string");
      Py_XDECREF(item);
      if (PyErr_Occurred()) {
        /* write_error_log(__FILE__, __LINE__); */
        call_error_logger();
        return STATUS_ERROR;
      }
    }
  }
  return STATUS_ERROR;
}

response_status response_start(client_t *client) {
  response_status ret;
  if (client->status_code == 304) {
    return write_headers(client, NULL, 0, 0);
  }

  if (CheckFileWrapper(client->response)) {
    DEBUG("use sendfile");
    // enable_cork(client);
    ret = start_response_file(client);
    if (ret == STATUS_OK) {
      // sended header
      ret = process_sendfile(client);
    }
  } else {
    ret = start_response_write(client);
    DEBUG("start_response_write status_code %d ret = %d", client->status_code,
          ret);
    if (ret == STATUS_OK) {
      // sended header
      ret = process_write(client);
    }
  }
  return ret;
}

void setup_start_response(void) {
  start_response = PyObject_NEW(ResponseObject, &ResponseObjectType);
}

void clear_start_response(void) { Py_CLEAR(start_response); }

PyObject *create_start_response(client_t *cli) {
  start_response->cli = cli;
  return (PyObject *)start_response;
}

static void ResponseObject_dealloc(ResponseObject *self) {
  self->cli = NULL;
  PyObject_DEL(self);
}

static PyObject *create_status(PyObject *bytes, int bytelen, int http_minor) {
  buffer_result r;
  buffer_t *b = new_buffer(256, 0);
  if (b == NULL) {
    return NULL;
  }

  if (http_minor == 1) {
    r = write2buf(b, "HTTP/1.1 ", 9);
  } else {
    r = write2buf(b, "HTTP/1.0 ", 9);
  }
  if (r != WRITE_OK) {
    goto error;
  }
  r = write2buf(b, PyBytes_AS_STRING(bytes), bytelen);
  if (r != WRITE_OK) {
    goto error;
  }
  r = write2buf(b, "\r\n", 2);
  if (r != WRITE_OK) {
    goto error;
  }
  return getPyString(b);
error:
  free_buffer(b);
  return NULL;
}

static PyObject *ResponseObject_call(PyObject *obj, PyObject *args,
                                     PyObject *kw) {
  PyObject *status = NULL, *headers = NULL, *exc_info = NULL, *bytes = NULL;
  char *status_code = NULL;
  char *status_line = NULL;
  int bytelen = 0, int_code;
  ResponseObject *self = NULL;
  char *buf = NULL;

  self = (ResponseObject *)obj;
#ifdef PY3
  if (!PyArg_ParseTuple(args, "UO|O:start_response", &status, &headers,
                        &exc_info)) {
    return NULL;
  }
#else
  if (!PyArg_ParseTuple(args, "SO|O:start_response", &status, &headers,
                        &exc_info)) {
    return NULL;
  }
#endif

  if (self->cli->headers != NULL && exc_info && exc_info != Py_None) {
    // Re-raise original exception if headers sent

    PyObject *type = NULL;
    PyObject *value = NULL;
    PyObject *traceback = NULL;

    if (!PyArg_ParseTuple(exc_info, "OOO", &type, &value, &traceback)) {
      return NULL;
    }

    Py_INCREF(type);
    Py_INCREF(value);
    Py_INCREF(traceback);
    // raise
    PyErr_Restore(type, value, traceback);
    return NULL;
  }

  if (self->cli->headers != NULL) {
    PyErr_SetString(PyExc_TypeError, "headers already set");
    return NULL;
  }

  if (!PyList_Check(headers)) {
    PyErr_SetString(PyExc_TypeError, "response headers must be a list");
    return NULL;
  }

  bytes = wsgi_to_bytes(status);
  bytelen = PyBytes_GET_SIZE(bytes);
  buf = PyMem_Malloc(sizeof(char *) * bytelen);
  if (!buf) {
    return NULL;
  }
  status_line = buf;
  strcpy(status_line, PyBytes_AS_STRING(bytes));

  /* DEBUG("%s :%d", (char*)status_line, bytelen); */

  if (!*status_line) {
    PyErr_SetString(PyExc_ValueError, "status message was not supplied");
    Py_XDECREF(bytes);
    if (buf) {
      PyMem_Free(buf);
    }
    return NULL;
  }

  status_code = strsep((char **)&status_line, " ");

  errno = 0;
  int_code = strtol(status_code, &status_code, 10);

  if (*status_code || errno == ERANGE) {
    PyErr_SetString(PyExc_TypeError, "status value is not an integer");
    Py_XDECREF(bytes);
    if (buf) {
      PyMem_Free(buf);
    }
    return NULL;
  }

  if (int_code < 100 || int_code > 999) {
    PyErr_SetString(PyExc_ValueError, "status code is invalid");
    Py_XDECREF(bytes);
    if (buf) {
      PyMem_Free(buf);
    }
    return NULL;
  }

  self->cli->status_code = int_code;

  Py_XDECREF(self->cli->headers);
  self->cli->headers = headers;
  Py_INCREF(self->cli->headers);

  Py_XDECREF(self->cli->http_status);

  self->cli->http_status =
      create_status(bytes, bytelen, self->cli->http_parser->http_minor);
  /* if(self->cli->http_parser->http_minor == 1){ */
  /* self->cli->http_status =  PyBytes_FromFormat("HTTP/1.1 %s\r\n",
   * PyBytes_AS_STRING(bytes)); */
  /* }else{ */
  /* self->cli->http_status =  PyBytes_FromFormat("HTTP/1.0 %s\r\n",
   * PyBytes_AS_STRING(bytes)); */
  /* } */

  /* DEBUG("set http_status %p", self->cli); */
  Py_XDECREF(bytes);
  if (buf) {
    PyMem_Free(buf);
  }
  Py_RETURN_NONE;
}

static PyObject *FileWrapperObject_new(PyObject *self, PyObject *filelike,
                                       size_t blksize) {
  FileWrapperObject *f;
  f = PyObject_NEW(FileWrapperObject, &FileWrapperType);
  if (f == NULL) {
    return NULL;
  }

  f->filelike = filelike;
  Py_INCREF(f->filelike);
  GDEBUG("alloc FileWrapperObject %p", f);
  return (PyObject *)f;
}

static PyObject *FileWrapperObject_iter(PyObject *o) {
  FileWrapperObject *self = (FileWrapperObject *)o;
  PyObject *iterator = PyObject_GetIter(self->filelike);
  if (iterator == NULL) {
    PyErr_SetString(PyExc_TypeError,
                    "file-like object must be a iterable object");
    return NULL;
  }
  DEBUG("use FileWrapperObject_iter");
  return (PyObject *)iterator;
}

static void FileWrapperObject_dealloc(FileWrapperObject *self) {
  GDEBUG("dealloc FileWrapperObject %p", self);
  Py_XDECREF(self->filelike);
  PyObject_DEL(self);
}

static PyObject *FileWrapperObject_close(FileWrapperObject *self,
                                         PyObject *args) {
  PyObject *method = NULL;
  PyObject *result = NULL;

  method = PyObject_GetAttrString(self->filelike, "close");

  if (method) {
    result = PyEval_CallObject(method, (PyObject *)NULL);
    if (!result) PyErr_Clear();
    Py_DECREF(method);
  }

  Py_XDECREF(result);
  Py_RETURN_NONE;
}

PyObject *file_wrapper(PyObject *self, PyObject *args) {
  PyObject *filelike = NULL;
  size_t blksize = 0;
  // PyObject *result = NULL;

  if (!PyArg_ParseTuple(args, "O|l:file_wrapper", &filelike, &blksize))
    return NULL;

  return FileWrapperObject_new(self, filelike, blksize);
}

int CheckFileWrapper(PyObject *obj) {
  FileWrapperObject *f;
  PyObject *filelike;
  int in_fd;
  if (obj->ob_type != &FileWrapperType) {
    return 0;
  }

  f = (FileWrapperObject *)obj;
  filelike = f->filelike;

  in_fd = PyObject_AsFileDescriptor(filelike);
  if (in_fd == -1) {
    PyErr_Clear();
    return 0;
  }

  return 1;
}

static PyMethodDef FileWrapperObject_method[] = {
    {"close", (PyCFunction)FileWrapperObject_close, METH_VARARGS, 0},
    {NULL, NULL}};

PyTypeObject ResponseObjectType = {
#ifdef PY3
    PyVarObject_HEAD_INIT(NULL, 0)
#else
    PyObject_HEAD_INIT(NULL) 0, /* ob_size */
#endif
        "meinheld.start_response",      /*tp_name*/
    sizeof(ResponseObject),             /*tp_basicsize*/
    0,                                  /*tp_itemsize*/
    (destructor)ResponseObject_dealloc, /*tp_dealloc*/
    0,                                  /*tp_print*/
    0,                                  /*tp_getattr*/
    0,                                  /*tp_setattr*/
    0,                                  /*tp_compare*/
    0,                                  /*tp_repr*/
    0,                                  /*tp_as_number*/
    0,                                  /*tp_as_sequence*/
    0,                                  /*tp_as_mapping*/
    0,                                  /*tp_hash */
    ResponseObject_call,                /*tp_call*/
    0,
    /*ResponseObject_str*/  /*tp_str*/
    0,                      /*tp_getattro*/
    0,                      /*tp_setattro*/
    0,                      /*tp_as_buffer*/
    Py_TPFLAGS_DEFAULT,     /*tp_flags*/
    "wsgi start_response ", /* tp_doc */
    0,                      /* tp_traverse */
    0,                      /* tp_clear */
    0,                      /* tp_richcompare */
    0,                      /* tp_weaklistoffset */
    0,                      /* tp_iter */
    0,                      /* tp_iternext */
    0,                      /* tp_methods */
    0,                      /* tp_members */
    0,                      /* tp_getset */
    0,                      /* tp_base */
    0,                      /* tp_dict */
    0,                      /* tp_descr_get */
    0,                      /* tp_descr_set */
    0,                      /* tp_dictoffset */
    0,                      /* tp_init */
    0,                      /* tp_alloc */
    0,                      /* tp_new */
};

PyTypeObject FileWrapperType = {
#ifdef PY3
    PyVarObject_HEAD_INIT(NULL, 0)
#else
    PyObject_HEAD_INIT(NULL) 0, /* ob_size */
#endif
        "meinheld.file_wrapper",           /*tp_name*/
    sizeof(FileWrapperObject),             /*tp_basicsize*/
    0,                                     /*tp_itemsize*/
    (destructor)FileWrapperObject_dealloc, /*tp_dealloc*/
    0,                                     /*tp_print*/
    0,                                     /*tp_getattr*/
    0,                                     /*tp_setattr*/
    0,                                     /*tp_compare*/
    0,                                     /*tp_repr*/
    0,                                     /*tp_as_number*/
    0,                                     /*tp_as_sequence*/
    0,                                     /*tp_as_mapping*/
    0,                                     /*tp_hash */
    0,                                     /*tp_call*/
    0,                                     /*tp_str*/
    0,                                     /*tp_getattro*/
    0,                                     /*tp_setattro*/
    0,                                     /*tp_as_buffer*/
    Py_TPFLAGS_DEFAULT,                    /*tp_flags*/
    "wsgi file_wrapper",                   /* tp_doc */
    0,                                     /* tp_traverse */
    0,                                     /* tp_clear */
    0,                                     /* tp_richcompare */
    0,                                     /* tp_weaklistoffset */
    FileWrapperObject_iter,                /* tp_iter */
    0,                                     /* tp_iternext */
    FileWrapperObject_method,              /* tp_methods */
    0,                                     /* tp_members */
    0,                                     /* tp_getset */
    0,                                     /* tp_base */
    0,                                     /* tp_dict */
    0,                                     /* tp_descr_get */
    0,                                     /* tp_descr_set */
    0,                                     /* tp_dictoffset */
    0,                                     /* tp_init */
    0,                                     /* tp_alloc */
    0,                                     /* tp_new */
};
