#include "ehttpServer.h"
#include "ecrypto.h"
#include "efile.h"
#include "ehtml.h"

#ifndef _WIN32
#include <unistd.h>
#endif

#ifdef EUTILS_HAVE_MD5_H
 #include <openssl/md5.h>
#endif

estr makeURI(const estr& host,const estr& location,estrarray& vars)
{
  estr tmpstr;
  tmpstr = "http://" + host + location;
  if (vars.size()){
    tmpstr+="?";
    tmpstr+=vars.join("&","=");
  }
  return(tmpstr);
}



ehttpServerConnection::ehttpServerConnection(): protstate(0)
{
//       "Date: Wed, 27 Feb 2008 10:46:15 GMT\r\n"
  sendHeaders["Server"]="konceptFX 1.0";
  sendHeaders["Cache-Control"]="no-store, no-cache, must-revalidate";
  sendHeaders["Content-Security-Policy"]="connect-src ws: 'self'";
//  sendHeaders["Cache-Control"]="post-check=0, pre-check=0";
  sendHeaders["Keep-Alive"]="timeout=65535, max=15";
  sendHeaders["Connection"]="Keep-Alive";
}

void ehttpServerConnection::doSend()
{
  if (!sendbuffer.len() || protstate<4){
    return;
  }

  int len;
  len=esocket::send(sendbuffer);
  cout << "# state: " << protstate << " sending data: " << sendbuffer.len() << " bytes sent: " << len << endl;
//  fout.flush();
  if (len<sendbuffer.len()) {
    if (len>0)
      sendbuffer.del(0,len);
    enableWriteCallback();
    return;
  }

  if (protstate!=5)
    protstate=0;
  sendbuffer.clear();
  disableWriteCallback();
}

bool ehttpServerConnection::wsSend(unsigned char op,const estr& data)
{
  if (protstate!=5){ lerror("tried to send without being in websocket: "+location+" protstate: "+protstate); return(false); }
//  ldieif(data.size()>65535,"sending data frames larger than 65535 not implemented yet");

  estr tmpstr;
  tmpstr.reserve(10);
  unsigned char *pstr=reinterpret_cast<unsigned char*>(tmpstr._str);
  pstr[0]=0x80|op;
  if (data.size()>=65535){
    pstr[1]=127;
    pstr[2]=data.size()>>(8*7);
    pstr[3]=data.size()>>(8*6);
    pstr[4]=data.size()>>(8*5);
    pstr[5]=data.size()>>(8*4);
    pstr[6]=data.size()>>(8*3);
    pstr[7]=data.size()>>(8*2);
    pstr[8]=data.size()>>8;
    pstr[9]=data.size();
    tmpstr._strlen=10;
  }else if (data.size()>=126){
    pstr[1]=126;
    pstr[2]=data.size()/256;
    pstr[3]=data.size()%256;
    tmpstr._strlen=4;
  }else{
    pstr[1]=data.len();
    tmpstr._strlen=2;
  }

  sendbuffer+=tmpstr;
  sendbuffer+=data;
  doSend();  
  return(true);
}



bool ehttpServerConnection::send(const estr& data,int status,const estr& mimetype)
{
  if (protstate!=3){ lerror("tried to send without request: location: "+location+" protstate: "+protstate); return(false); }
  estr headerstr;
  estr statstr;
  switch (status){
    case 101: statstr="Switching Protocols"; break;
    case 200: statstr="OK"; break;
    case 301: statstr="Moved Permanently"; break;
    case 404: statstr="Not found"; break;
   default:
    statstr="none";
  }

  sendHeaders["Content-Length"]=estr(data.len());
  if (mimetype.len()>0)
    sendHeaders["Content-Type"]=mimetype;

  headerstr=sendHeaders.join("\r\n",": ");

  cout << "location: " << location << " sendHeaders: "+headerstr<<endl;
  estr tmpstr;

  tmpstr+="HTTP/1.1 "+estr(status)+" "+statstr+"\r\n";
  tmpstr+=headerstr;
  tmpstr+="\r\n\r\n";
  tmpstr+=data;


  lerrorif(sendbuffer.size()>0,"Data still in buffer");
  protstate=4;
  sendbuffer+=tmpstr;
  doSend();
  return(true);
/*
  int len;
  len=esocket::send(tmpstr);
  
  if (len==0){lerror("connection was closed"); close(); doClose(); return(false);}
  
  estr sendbuffer;
  if (len < tmpstr.len()) {
    if (len <= 0){
      close();
      doClose();
      lerror("connection closed! Unable to send data: "+estr(len)+" --- "+estr(tmpstr.len())+" --- "+estr(data.len()));
      return(false);
    }else{
      lwarn("did not send whole data: "+estr(len)+" --- "+estr(tmpstr.len()));
      sendbuffer += tmpstr.substr(len);
      lwarn("sendbuffer: "+estr(sendbuffer.len()));
      do {
        sleep(1);
        len=esocket::send(sendbuffer);
	lwarn("sent rest: "+estr(len)+" of "+estr(tmpstr.len()));
	if (len<=0) { lerror("connection closed! Unable to send data: "+estr(len)+" --- "+estr(tmpstr.len())+" --- "+estr(data.len())); return(false); }
        sendbuffer.del(0,len);
	lwarn("remaining rest: "+estr(sendbuffer.len()));
      } while (sendbuffer.len());
    }
  }
  protstate=0;
  return(true);
*/
}



void ehttpServerConnection::sendRedirect(const estr& newurl)
{
  sendHeaders["Location"]=newurl;
  send("<!DOCTYPE HTML PUBLIC \"-//IETF//DTD HTML 2.0//EN\">"
       "<html><head>"
       "<title>301 Moved</title>"
       "</head><body>"
       "<h1>Moved</h1>"
       "<p>This page has moved to <a href=\""+newurl+"\">"+newurl+"</a>.</p>"
       "<hr>"
       "<address>KonceptFX 1.0</address>"
       "</body></html>",301);
  linfo("Redirected to: "+newurl);
}


void ehttpServerConnection::sendError(int err,const estr& errtitle,const estr& errmsg)
{
  send("<!DOCTYPE HTML PUBLIC \"-//IETF//DTD HTML 2.0//EN\">"
       "<html><head>"
       "<title>"+estr(err)+" "+errtitle+" </title>"
       "</head><body>"
       "<h1>"+errtitle+"</h1>"
       "<p>"+errmsg+"</p>"
       "<hr>"
       "<address>KonceptFX 1.0</address>"
       "</body></html>",err);
  lerror("sent error: "+estr(err)+" "+errtitle+" "+errmsg);
}

void ehttpServerConnection::sendNotFound()
{
  sendError(404,"Not Found","The requested URL "+location+" was not found on this server.");
}

estrarray mimetypes="gif=image/gif,png=image/png,jpg=image/jpg,swf=application/x-shockwave-flash,html=text/html,js=text/javascript,css=text/css";

void ehttpServerConnection::sendFile(const estr& filename)
{
  efile file(filename);
  estr data;
  estr mimetype;

  if (file.exists()){
    file.read(data);
    if (mimetypes.findkey(file.extension())!=-1)
      send(data,200,mimetypes[file.extension()]);
    else{
      send(data,200);
      lwarn("no mimetype defined for extension: "+file.extension()+" using default: text/html");
    }
    file.close();
  }else{
    lerror("Tried to send file: "+filename+" but it was not found");
    sendNotFound();
  }
}

void ehttpServerConnection::parseLocation(estr& location,estrarray& vars)
{
  int i,j;
  i=location.find("?");
  if (i==-1) return;

  estrarray tmparr=location.substr(i+1).explode("&");
  location=location.substr(0,i);
  for (i=0; i<tmparr.size(); ++i){
    j=tmparr[i].find("=");
    if (j==-1)
      vars[tmparr[i]]="";
    else
      vars[tmparr[i].substr(0,j)]=tmparr[i].substr(j+1);
  }
}

void ehttpServerConnection::doWSRecvText(const estr& data)
{
}

void ehttpServerConnection::doWSRecvBinary(const estr& data)
{
}

void ehttpServerConnection::doWSRecvPong(const estr& data)
{
}


void ehttpServerConnection::doRecv()
{
  int i;
  estr line;
  estr tmpdata;
  estrarray tmparr;

  recv(tmpdata);
  cout << "Received data: " << protstate << " " << tmpdata.len() << " " << data.len() << endl;

  data+=tmpdata;

  while (protstate==5 && data.len()){
    
    unsigned char *pdata=reinterpret_cast<unsigned char*>(data._str);
    cerr << "# received websocket data: " << data.len() << endl;
    // websocket mode
    if (data.len()<2) return;
    uint8_t fin=pdata[0]&0x80u;
    uint8_t op=pdata[0]&0x0Fu;
    uint8_t maskbit=pdata[1]&0x80u;
    uint64_t l=pdata[1]&0x7Fu;
    uint32_t i=2u;
    cerr << " fin: " << fin << " op: " << op << " maskbit: " << maskbit << " l: " << l << endl;
    if (l==126){
      if (data.len()<4) return;
      l=(uint16_t(pdata[2])<<8u)|uint16_t(pdata[3]);
      i=4u;
      cerr << "l=126: 2byte length: " << l << endl;
    }else if (l==127){
      if (data.len()<10) return;
      l=(uint64_t(pdata[2])<<56u)|(uint64_t(pdata[3])<<48u)|(uint64_t(pdata[4])<<40u)|(uint64_t(pdata[5])<<32u)|(uint64_t(pdata[6])<<24u)|(uint64_t(pdata[7])<<16u)|(uint64_t(pdata[8])<<8u)|uint64_t(pdata[9]);
      cerr << "l=127: 8byte length: " << l << endl;
      i=10u;
    }else{
      cerr << "1byte(7bit) length: " << l << endl;
    }
    uint32_t mask=0u;
    if (maskbit){
      if (data.len()<i+4u) return;
      mask=(uint32_t(pdata[i+3])<<24u)|(uint32_t(pdata[i+2])<<16u)|(uint32_t(pdata[i+1])<<8u)|uint32_t(pdata[i]);
      i+=4u;
    }
    l+=i;
    cerr << "# received websocket data: " << data.len() << " l: " << l << endl;
    if (data.len()<l) return; // frame has not been received yet

    if (maskbit){
      for (int j=i; j<l; ++j)
        pdata[j]^=mask>>(((j-i)%4)*8u);
    }

    switch (op){
      case 0x0Au: // pong
        doWSRecvPong(data.substr(i,l-i));
       break;
      case 0x09u: // ping
        wsSend(0x0A,data.substr(i,l-i));
       break;
      case 0x08u: // connection close
        wsSend(0x08,data.substr(i,l-i));
        close();
       break;
      case 0x00u: // continuation frame must have previous frame with fin=0 shoudl have fin=1 at some point
       break;
      case 0x01u: // text frame
        doWSRecvText(data.substr(i,l-i));
        cerr << "text frame: " << data.substr(i,l-i) << endl;
       break;
      case 0x02u: // binary frame
        cerr << "binary frame: " << data.substr(i,l-i) << endl;
        doWSRecvBinary(data.substr(i,l-i));
       break;
      default:
        lerror("received unknown websocket frame: "+estr(int(op)));
    }
    if (l==data.len()) data.clear();
    else data.del(0,l);
  }

  if (protstate==0){
    if (!data.getline(line)) return;

    requestHeaders.clear();
    tmparr = line.explode(" ");
    if (tmparr.size() < 3) { lerror("malformed request: "+line); close(); doClose(); return; }
    cmd = tmparr[0];
    getVars.clear();
    location = tmparr[1];
    request_uri = tmparr[1];
    parseLocation(location,getVars);
    protocol = tmparr[2];
    ++protstate;
    cout << "got headers" << endl;
  }

  while (protstate==1){
    if (!data.getline(line)) return;
//    cout << "HTTP REQUEST line: " << line << endl;

    if (line.len()==0)
      { ++protstate; break; }

    i = line.find(":");
    if (i==-1)
      { lerror("missing header separator: "+line); cout << request_uri << endl; cout << requestHeaders << endl; continue; }

    requestHeaders.add(line.substr(0,i),line.substr(i+1).trim());
  }

  if (protstate==2){
    if (requestHeaders.findkey("Host")!=-1)
      request_uri="http://"+requestHeaders["Host"]+request_uri;
    if (requestHeaders.findkey("Upgrade")!=-1 && requestHeaders["Upgrade"]=="websocket" && requestHeaders.findkey("Connection")!=-1 && requestHeaders["Connection"].find("Upgrade")!=-1){
      cerr << "# got websocket request" << endl;
      if (requestHeaders.findkey("Sec-WebSocket-Key")==-1) { sendError(400,"Sec-WebSocket-Key missing","Missing websocket request parameter"); return; }
      estr wskey=requestHeaders["Sec-WebSocket-Key"];
      estr rwskey=wskey+"258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
      estr rwskeydig;
      protstate=3;
#ifdef EUTILS_HAVE_MD5_H
      sha1(rwskey,rwskeydig);
#else
      cerr << "# websocket not supported" << endl;
      sendError(400,"WebSocket not supported","Not supported");
      return;
#endif
      rwskey=base64encode(rwskeydig);
      sendHeaders.clear();
      sendHeaders.add("Upgrade","websocket");
      sendHeaders.add("Connection","Upgrade");
      sendHeaders.add("Sec-WebSocket-Accept",rwskey);
      cerr << "Sec-WebSocket-Accept: " << rwskey << endl;
      send(estr(),101,estr());
      protstate=5;
      cerr << "# switched to websocket" << endl;
      doHandleWS(location);
      return;
    }
      
    linfo("got request: "+cmd+" "+location);
  }

//  if (protstate>=2&&requestHeaders.findkey("User-Agent")!=-1 && requestHeaders["User-Agent"].find("Firefox")!=-1)
//    cout << "RECVDATA:"<<endl<<tmpdata<<endl;

  if (cmd=="GET" && protstate==2){
    ++protstate;
    doHandleGet(location);
  } else if (cmd=="POST" && protstate==2){
    int postlen;
    if (requestHeaders.findkey("Content-Length")==-1) { lerror("Malformed POST request"); close(); doClose(); return; }

    postlen = requestHeaders["Content-Length"].i();
    if (data.len()<postlen) return;

    postdata=data.substr(0,postlen);
    data.del(0,postlen);
    ++protstate;
    doHandlePost(location);
  }
}

