#include "AmMimeBody.h"
#include "sip/parse_common.h"
#include "sip/parse_header.h"
#include "sip/defs.h"

#include "log.h"
#include "AmUtils.h"

#include <memory>
using std::unique_ptr;

#define MULTIPART             "multipart"
#define MULTIPART_MIXED       "multipart/mixed"
#define MULTIPART_ALTERNATIVE "multipart/alternative"

#define BOUNDARY_str "boundary"
#define BOUNDARY_len (sizeof(BOUNDARY_str)-/*0-term*/1)

AmMimeBody::AmMimeBody(const AmMimeBody& body)
  : ct(body.ct),
    hdrs(body.hdrs)
{
  if(!body.payload_s.empty()) {
    setPayload(body.payload_s);
  }

  for(Parts::const_iterator it = body.parts.begin();
      it != body.parts.end(); ++it) {
    parts.push_back(new AmMimeBody(**it));
  }
}

AmMimeBody::~AmMimeBody()
{
  clearParts();
  clearPayload();
}

const AmMimeBody& AmMimeBody::operator = (const AmMimeBody& r_body)
{
  ct = r_body.ct;
  hdrs = r_body.hdrs;

  if(!r_body.payload_s.empty()) {
    setPayload(r_body.payload_s);
  }
  else {
    clearPayload();
  }

  clearParts();
  for(Parts::const_iterator it = r_body.parts.begin();
      it != r_body.parts.end(); ++it) {
    parts.push_back(new AmMimeBody(**it));
  }

  return r_body;
}

AmContentType::AmContentType()
  : mp_boundary(NULL), length(-1)
{
}

AmContentType::AmContentType(const AmContentType& ct)
  : type(ct.type),
    subtype(ct.subtype),
    mp_boundary(NULL),
    length(ct.length)
{
  for(Params::const_iterator it = ct.params.begin();
      it != ct.params.end(); ++it) {
    params.push_back(new Param(**it));
    if((*it)->type == Param::BOUNDARY)
      mp_boundary = params.back();
  }
}

AmContentType::~AmContentType()
{
  clearParams();
}

const AmContentType& AmContentType::operator = (const AmContentType& r_ct)
{
  type = r_ct.type;
  subtype = r_ct.subtype;

  clearParams();
  for(Params::const_iterator it = r_ct.params.begin();
      it != r_ct.params.end(); ++it) {
    params.push_back(new Param(**it));
    if((*it)->type == Param::BOUNDARY)
      mp_boundary = params.back();
  }

  return r_ct;
}

void AmContentType::clearParams()
{
  mp_boundary = NULL;
  while(!params.empty()){
    delete params.front();
    params.pop_front();
  }
}

void AmContentType::resetBoundary()
{
  Params::iterator it = params.begin();
  while (it != params.end()) {
    Params::iterator l_it = it;
    it++;
    if ((*l_it)->type == Param::BOUNDARY) {
      delete *l_it;
      params.erase(l_it);
    }
  }

  params.push_back(new Param(BOUNDARY_str, int2hex(get_random())));
  params.back()->type = Param::BOUNDARY;
  mp_boundary = params.back();
}

void AmMimeBody::clearParts()
{
  while(!parts.empty()){
    delete parts.front();
    parts.pop_front();
  }
}

void AmMimeBody::clearPart(Parts::iterator position)
{
  parts.erase(position);
}

void AmMimeBody::clearPayload()
{
  payload_s = "";
}

int AmContentType::parse(const string& ct)
{
  enum {
    CT_TYPE=0,
    CT_SLASH_SWS,
    CT_SUBTYPE_SWS,
    CT_SUBTYPE
  };

  const char* c = ct.c_str();
  const char* beg = c;
  const char* end = c + ct.length();

  int st = CT_TYPE;
  int saved_st = 0;

  for(;c < end; c++) {
    switch(st){

    case CT_TYPE:
      switch(*c) {
      case_CR_LF;
      case SP:
      case HTAB:
	type = string(beg,c-beg);
	st = CT_SLASH_SWS;
	break;
	
      case SLASH:
	type = string(beg,c-beg);
	st = CT_SUBTYPE_SWS;
	break;
      }
      break;

    case CT_SLASH_SWS:
      switch(*c){
      case_CR_LF;
      case SP:
      case HTAB:
	break;

      case SLASH:
	st = CT_SUBTYPE_SWS;
	break;

      default:
	DBG("Missing '/' after media type in 'Content-Type' hdr field\n");
	return -1;
      }
      break;

    case CT_SUBTYPE_SWS:
      switch(*c){
      case_CR_LF;
      case SP:
      case HTAB:
	break;

      default:
	st = CT_SUBTYPE;
	beg = c;
	break;
      }
      break;

    case CT_SUBTYPE:
      switch(*c){

      case_CR_LF;

      case SP:
      case HTAB:
      case SEMICOLON:
	subtype = string(beg,c-beg);
	return parseParams(c,end);
      }
      break;

    case_ST_CR(*c);
    case ST_LF:
    case ST_CRLF:
      switch(saved_st){
      case CT_TYPE:
	if(!IS_WSP(*c)){
	  // should not happen: parse_headers() should already 
	  //                    have triggered an error
	  DBG("Malformed Content-Type value: <%.*s>\n",(int)(end-beg),beg);
	  return -1;
	}
	else {
	  type = string(beg,(c-(st==ST_CRLF?2:1))-beg);
	  saved_st = CT_SLASH_SWS;
	}
	break;

      case CT_SLASH_SWS:
      case CT_SUBTYPE_SWS:
	if(!IS_WSP(*c)){
	  // should not happen: parse_headers() should already 
	  //                    have triggered an error
	  DBG("Malformed Content-Type value: <%.*s>\n",(int)(end-beg),beg);
	  return -1;
	}
	break;

      case CT_SUBTYPE:
	subtype = string(beg,(c-(st==ST_CRLF?2:1))-beg);
	if(!IS_WSP(*c)){
	  // should not happen: parse_headers() should already 
	  //                    have triggered an error
	  return 0;
	}
	return parseParams(c,end);
      }
      
      st = saved_st;
      break;
    }
  }

  // end-of-string
  switch(st){
  case CT_TYPE:
  case CT_SLASH_SWS:
  case CT_SUBTYPE_SWS:
    DBG("Malformed Content-Type value: <%.*s>\n",(int)(end-beg),beg);
    return -1;
    
  case CT_SUBTYPE:
    subtype = string(beg,c-beg);
    break;
  }
  
  return 0;
}

int AmContentType::parse(const string& ct, unsigned int _length)
{
  if (!length) {
    DBG("Cannot parse Content-Type of zero-length body\n");
    return -1;
  }
  length = _length;
  return parse(ct);
}

int  AmContentType::parseParams(const char* c, const char* end)
{
  list<sip_avp*> avp_params;
  if(parse_gen_params_sc(&avp_params, &c, end-c, '\0') < 0) {
    if(!avp_params.empty()) free_gen_params(&avp_params);
    return -1;
  }
  
  for(list<sip_avp*>::iterator it_ct_param = avp_params.begin();
      it_ct_param != avp_params.end();++it_ct_param) {

    DBG("parsed new content-type parameter: <%.*s>=<%.*s>",
	(*it_ct_param)->name.len,(*it_ct_param)->name.s,
	(*it_ct_param)->value.len,(*it_ct_param)->value.s);

    Param* p = new Param(c2stlstr((*it_ct_param)->name),
			 c2stlstr((*it_ct_param)->value));

    if(p->parseType()) {
      free_gen_params(&avp_params);
      delete p;
      return -1;
    }
    
    if(p->type == Param::BOUNDARY)
      mp_boundary = p;

    params.push_back(p);
  }

  free_gen_params(&avp_params);
  return 0;
}

int AmContentType::Param::parseType()
{
  const char* c = name.c_str();
  unsigned  len = name.length();
  
  switch(len){
  case BOUNDARY_len:
    if(!lower_cmp(c,BOUNDARY_str,len)){
      if(value.empty()) {
	DBG("Content-Type boundary parameter is missing a value\n");
	return -1;
      }
      type = Param::BOUNDARY;
    }
    else type = Param::OTHER;
    break;
  default:
    type = Param::OTHER;
    break;
  }

  return 0;
}

int AmMimeBody::findNextBoundary(unsigned char** beg, unsigned char** end)
{
  enum {
    B_START=0,
    B_CR,
    B_LF,
    B_HYPHEN,
    B_BOUNDARY,
    B_HYPHEN2,
    B_HYPHEN3,
    B_CR2,
    B_LF2,
    B_MATCHED
  };

  if(!ct.mp_boundary)
    return -1;

  unsigned char* c = *beg;
  unsigned char* b_ini = (unsigned char*)ct.mp_boundary->value.c_str();
  unsigned char* b = b_ini;
  unsigned char* b_end = b + ct.mp_boundary->value.length();

  int st=B_START;

  // Allow the buffer to start directly 
  // with the boundary delimiter
  if(*c == HYPHEN)
    st=B_LF;

#define case_BOUNDARY(_st_,_ch_,_st1_)		\
               case _st_:			\
		 switch(*c){			\
		 case _ch_:			\
		   st = _st1_;			\
		   break;			\
		 default:			\
		   st = B_START;		\
		   break;			\
		 }				\
		 break

#define case_BOUNDARY2(_st_,_ch_,_st1_)		\
               case _st_:			\
		 switch(*c){			\
		 case _ch_:			\
		   st = _st1_;			\
		   break;			\
		 default:			\
		   b = b_ini;			\
		   is_final = false;		\
		   st = B_START;		\
		   break;			\
		 }				\
		 break


  bool is_final = false;

  for(;st != B_MATCHED && (c < *end-(b_end-b)); c++){

    switch(st){
    case B_START: 
      if(*c == CR) {
	*beg = c;
	st = B_CR;
      }
      break;

    case_BOUNDARY(B_CR,         LF, B_LF);

    case B_LF:
      switch(*c){
      case HYPHEN:
	st = B_HYPHEN;
	break;
      case CR:
	st = B_CR;
	break;
      default:
	st = B_START;
	break;
      }
      break;

    case_BOUNDARY(B_HYPHEN, HYPHEN, B_BOUNDARY);

    case B_BOUNDARY:
      if(*c == *b) {
	if(++b == b_end) {
	  // reached end boundary buffer
	  st = B_HYPHEN2;
	}
      }
      else {
	b = b_ini;
	st = B_START;
      }
      break;

    case B_HYPHEN2:
      switch(*c) {
      case HYPHEN: 
	is_final = true; 
	st = B_HYPHEN3; 
	break;

      case CR:
	st = B_LF2;
	break;

      default:
	b = b_ini;
	st = B_START;
	break;
      }
      break;

    case_BOUNDARY2(B_HYPHEN3, HYPHEN, B_CR2);
    case_BOUNDARY2(B_CR2,         CR, B_LF2);
    case_BOUNDARY2(B_LF2,         LF, B_MATCHED);
    }
  }

  if(st == B_MATCHED || (st == B_HYPHEN3) || (st == B_CR2) || (st == B_LF2)) {
    *end = c;
    return is_final ? 1 : 0;
  }

  return -1;
}

int AmMimeBody::parseSinglePart(unsigned char* buf, unsigned int len)
{
  list<sip_header*> hdrs;
  const char* c = (char*)buf;
  const char* end = c + len;

  // parse headers first
  if(parse_headers(hdrs,&c,c+len) < 0) {
    DBG("could not parse part headers\n");
    free_headers(hdrs);
    return -1;
  }

  unique_ptr<AmMimeBody> sub_part(new AmMimeBody());

  string sub_part_hdrs;
  string sub_part_ct;
  for(list<sip_header*>::iterator it = hdrs.begin();
      it != hdrs.end(); ++it) {

    DBG("Part header: <%.*s>: <%.*s>\n",
	(*it)->name.len,(*it)->name.s,
	(*it)->value.len,(*it)->value.s);

    if((*it)->type == sip_header::H_CONTENT_TYPE) {
      sub_part_ct = c2stlstr((*it)->value);
    }
    else {
      sub_part_hdrs += c2stlstr((*it)->name) + COLSP
	+ c2stlstr((*it)->value) + CRLF;
    }
  }

  if(!sub_part_ct.empty() &&
     (sub_part->parse(sub_part_ct,c,end-c) == 0)) {
    // TODO: check errors
    DBG("Successfully parsed subpart.\n");
  }
  else {
    DBG("Either no Content-Type, or subpart parsing failed.\n");
    sub_part->ct.setType("");
    sub_part->ct.setSubType("");
    sub_part->setPayload(c,end-c);
  }

  sub_part->setHeaders(sub_part_hdrs);
  parts.push_back(sub_part.release());

  free_headers(hdrs);
  return 0;
}

int AmMimeBody::parseMultipart(const char* buf, unsigned int len)
{
  if(!ct.mp_boundary) {
    DBG("boundary parameter missing in a multipart MIME body\n");
    return -1;
  }

  unsigned char* buf_end;
  unsigned char* part_end;
  unsigned char* next_part;
  string temp_buffer;

  // if there are quotes around the boundary, we have to fix that
  if (ct.mp_boundary->value.front() == DQUOTE || ct.mp_boundary->value.back() == DQUOTE) {

    // cast original char array into a temporary string
    temp_buffer = reinterpret_cast<const char*>(buf);                   // create a temporary string to keep given buffer
    const string from = ct.mp_boundary->value;                          // value we are looking for
    const string to = from.substr(1, from.length() - 2);                // value to which we do a substitution

    size_t start_pos = 0;
    while((start_pos = temp_buffer.find(from, start_pos)) != string::npos) {
        temp_buffer.replace(start_pos, from.length(), to);
        start_pos += to.length();
    }

    int eventual_length = temp_buffer.length();

    /* TODO: wtf is this?, rework parsing here to work with std::string */
    buf_end   = (unsigned char*)temp_buffer.c_str() + eventual_length;
    part_end  = (unsigned char*)temp_buffer.c_str();
    next_part = (unsigned char*)buf_end;
  } else {
    buf_end  = (unsigned char*)buf + len;
    part_end  = (unsigned char*)buf;
    next_part = (unsigned char*)buf_end;
  }

  int err = findNextBoundary(&part_end,&next_part);
  if(err < 0) {
    DBG("unexpected end-of-buffer\n");
    return -1;
  }

  unsigned char* part_beg  = NULL;
  do {
    part_beg  = next_part;
    part_end  = part_beg;
    next_part = buf_end;

    err = findNextBoundary(&part_end,&next_part);
    if(err < 0) {
      DBG("unexpected end-of-buffer while searching for MIME body boundary\n");
      return -1;
    }
    
    if(parseSinglePart(part_beg,part_end-part_beg) < 0) {
      DBG("Failed parsing part\n");
    }
    else {
      AmMimeBody* part = parts.back();
      DBG("Added new part:\n%s\n", payload_s.c_str());
    }
    
  } while(!err);

  DBG("End-of-multipart body found\n");

  return 0;
}

int AmMimeBody::parse(const string& content_type,
		      const char* buf,
		      unsigned int len)
{
  if(ct.parse(content_type, len) < 0)
    return -1;
  
  if(ct.isType(MULTIPART)) {

    DBG("parsing multi-part body\n");
    return parseMultipart(buf,len);
  }
  else {
    DBG("saving single-part body\n");
    setPayload(buf,len);
  }

  return 0;
}

void AmMimeBody::convertToMultipart()
{
  AmContentType n_ct;
  /* TODO: should we somehow predict the length of multipart body? */
  n_ct.parse(MULTIPART_MIXED); // never fails
  n_ct.resetBoundary();

  AmMimeBody* n_part = new AmMimeBody(*this);
  n_part->ct = ct;

  parts.push_back(n_part);
  ct = n_ct;

  /* TODO: should we clear the original (root) payload when now having multi-part(s) stored? */
}

void AmMimeBody::convertToSinglepart()
{
  if (parts.size() == 1) {
    this->ct = parts.front()->ct;

    if (!parts.front()->payload_s.empty()) {
      setPayload(parts.front()->payload_s);
      clearParts();
      return;
    }
    else {
      WARN("Payload is empty.\n");
    }
  }

  WARN("body does not have exactly one part\n");
}

void AmContentType::setType(const string& t)
{
  type = t;
}

void AmContentType::setSubType(const string& st)
{
  subtype = st;
}

bool AmContentType::isType(const string& t) const
{
  return !lower_cmp_n(t.c_str(),t.length(),
		      type.c_str(),type.length());
}

bool AmContentType::isSubType(const string& st) const
{
  return !lower_cmp_n(st.c_str(),st.length(),
		      subtype.c_str(),subtype.length());
}


void AmMimeBody::setHeaders(const string& hdrs)
{
  this->hdrs = hdrs;
}

AmMimeBody* AmMimeBody::addPart(const string& content_type)
{
  DBG("Adding a new part to an existing body.\n");

  AmMimeBody* body = NULL;
  if(ct.type.empty() && ct.subtype.empty()) {
    // fill *this* body
    string sdp_body;
    print(sdp_body);
    unsigned int len = sdp_body.length(); /* the number of bytes represented by SDP body as a string */

    if(ct.parse(content_type, len)) {
      DBG("could not parse content-type\n");
      return NULL;
    }
    
    body = this;
  }
  else if(!ct.isType(MULTIPART)) {
    // convert to multipart
    convertToMultipart();
    body = new AmMimeBody();
    /* TODO: should we somehow predict multipart body's length? */
    if(body->ct.parse(content_type)) {
      DBG("parsing new content-type failed\n");
      delete body;
      return NULL;
    }

    // add new part
    parts.push_back(body);
  }
  
  return body;
}

int AmMimeBody::deletePart(const string& content_type)
{
  if (!ct.isType(MULTIPART)) {
    DBG("body is not multipart\n");
    return -1;
  }

  for(Parts::iterator it = parts.begin();
      it != parts.end(); ++it) {
    if((*it)->hasContentType(content_type)) {
      clearPart(it);
      if (parts.size() == 1) convertToSinglepart();
      return 0;
    }
  }

  DBG("no match");
  return -1;
}

void AmMimeBody::setPayload(const char* buf, unsigned int len)
{
  string tmp(buf, len);
  payload_s = std::move(tmp);
}

bool AmMimeBody::empty() const
{
  return (payload_s.empty() && parts.empty());
}

bool AmContentType::hasContentType(const string& content_type) const
{
  if(content_type.empty() && type.empty() && subtype.empty())
    return true;

  if(content_type.empty() != (type.empty() && subtype.empty()))
    return false;

  // Quick & dirty comparison, might not always be correct
  string cmp_ct = type + "/" + subtype;
  return !lower_cmp_n(cmp_ct.c_str(),cmp_ct.length(),
		      content_type.c_str(),content_type.length());
}

string AmContentType::getStr() const 
{
  if(type.empty() && subtype.empty())
    return "";

  return type + "/" + subtype; 
}

string AmContentType::getHdr() const
{
  string ct = getStr();
  if(ct.empty())
    return ct;

  for(Params::const_iterator it = params.begin();
      it != params.end(); ++it) {
    
    ct += ";" + (*it)->name + "=" + (*it)->value;
  }

  return ct;
}

bool AmMimeBody::isContentType(const string& content_type) const
{
  return ct.hasContentType(content_type);
}

AmMimeBody* AmMimeBody::hasContentType(const string& content_type)
{
  if(isContentType(content_type)) {
    return this;
  }
  else if(ct.isType(MULTIPART)) {
    for(Parts::iterator it = parts.begin();
	it != parts.end(); ++it) {
      
      if((*it)->hasContentType(content_type)) {
	return *it;
      }
    }
  }

  return NULL;
}

const AmMimeBody* AmMimeBody::hasContentType(const string& content_type) const
{
  if(isContentType(content_type)) {
    return this;
  }
  else if(ct.isType(MULTIPART)) {
    for(Parts::const_iterator it = parts.begin();
	it != parts.end(); ++it) {
      
      if((*it)->hasContentType(content_type)) {
	return *it;
      }
    }
  }

  return NULL;
}

void AmMimeBody::print(string& buf) const
{
  if(empty())
    return;

  if(!payload_s.empty()) {
    buf += payload_s;
  }
  else {

    // if (ct.mp_boundary == NULL)
    //   ct.resetBoundary(); 

    for(Parts::const_iterator it = parts.begin();
	it != parts.end(); ++it) {

      if (ct.mp_boundary != NULL && ct.mp_boundary->value.at(0) == '"' && ct.mp_boundary->value.back() == '"' )
      {
        DBG("Stripping doublequotes at the beginning and at the end of the boundary");
        ct.mp_boundary->value.erase(0,1);
        ct.mp_boundary->value.erase(ct.mp_boundary->value.size() - 1);
      }

      buf += "--" + (ct.mp_boundary != NULL ? ct.mp_boundary->value : string("") ) + CRLF;
      buf += SIP_HDR_CONTENT_TYPE COLSP + (*it)->getCTHdr() + CRLF;
      buf += (*it)->hdrs + CRLF;
      (*it)->print(buf);
      buf += CRLF;
    }

    if(!parts.empty()) {
      buf += "--" + (ct.mp_boundary != NULL ? ct.mp_boundary->value : string("") ) + "--" CRLF;
    }
  }
}
