#include <unistd.h>
#include <limits.h>
#include <stdlib.h>
#include <stdint.h>

#include <clock.h>
#include <zio.h>
#include <log.h>
#include <zloop.h>

#include "base64.h"
#include "io_buf.h"

struct web_client_http:ev::io
{
	in_buff m_buff;
	int m_data_len=-1;
	int m_mask_pos=-1;
	int m_data_pos=-1;
	int m_fd,m_status;

	web_client_http()
		:m_buff(8192)
	{
		m_fd=-1;
	}

	int connect_tcp(const char*ip,int port)
	{
		int fd=zio::build_stream();
		if(zio::connect(fd,ip,port))
		{
			zio::close(fd);
			return m_fd=-1;
		}

		zio::setiobuf(fd,16<<10,16<<10);
		zio::setblocking(fd,false);

		return m_fd=fd;
	}

	void ws_reset()
	{
		if(m_data_pos+m_data_len>0)
		{
			m_buff.free(m_data_len+m_data_pos);
		}

		m_data_len=-1;
		m_mask_pos=-1;
		m_data_pos=-1;
	}

	bool ws_fin()const
	{
		return (m_buff[0]&0x80) != 0;
	}

	bool ws_more()const
	{
		return (m_buff[0]&0xF) ==0 ;
	}

	bool ws_text()const
	{
		return (m_buff[0]&0xF) ==1 ;
	}

	bool ws_bin()const
	{
		return (m_buff[0]&0xF) ==2 ;
	}

	bool ws_ping()const
	{
		return (m_buff[0]&0xF) ==9 ;
	}

	bool ws_pong()const
	{
		return (m_buff[0]&0xF) ==10 ;
	}

	bool ws_close()const
	{
		return (m_buff[0]&0xF) ==8 ;
	}

	bool ws_mask()const
	{
		return (m_buff[1]&0x80) !=0 ;
	}

	int parse_length()
	{
		if(m_buff.len_data()<2)
			return -1;

		if(m_data_pos>0)
			return 0;

		if((m_buff[1]&0x7F)<=125)
		{
			m_mask_pos=ws_mask()?2:-1;
			m_data_pos=ws_mask()?6:2;

			m_data_len=m_buff[1]&0x7F;
		}
		else
		if((m_buff[1]&0x7F)==126)
		{
			if(m_buff.len_data()<4)
				return -1;

			m_mask_pos=ws_mask()?4:-1;
			m_data_pos=ws_mask()?8:4;

			m_data_len=(m_buff[2]<<8) | m_buff[3];
		}
		else
		{
			if(m_buff.len_data()<9)
				return -1;

			m_mask_pos=ws_mask()?9:-1;
			m_data_pos=ws_mask()?13:9;

			//不考虑大于20G的数据
			m_data_len=(m_buff[6]<<24)|(m_buff[7]<<16)|(m_buff[8]<<8)|(m_buff[9]);
		}
		
		int i=0;
		for(i=0;i<m_data_len;i++)
		{
			uint8_t b=m_buff[m_data_pos+i];
			if(b>=0x30 && b<=0x39)
				continue;
			break;
		}

		m_data_len-=i;
		m_data_pos+=i;

		return 0;
	}

	void mask(const uint8_t*m,uint8_t*d,int len)
	{
		for(int i=0;i<len;i++)
			d[i]=d[i]^m[i&3];
	}

	int ws_parse()
	{
		if(parse_length()<0)
			return -1;

		if(m_buff.len_data()<m_data_pos+m_data_len)
			return -1;

		if(ws_mask())
		{
			mask(&m_buff[m_mask_pos],&m_buff[m_data_pos],m_data_len);
		}

		for(int i=0;i<m_data_len;i++)
		{
			printf("%c",m_buff[m_data_pos+i]);
		}

		printf("\n\n");

		return 0;
	}


	int connect_ws(const char*ip,int port)
	{
		unsigned char k[16], k6[128];

		for(size_t i=0;i<sizeof(k);i++)
			k[i]=rand();

		base64::encode(k,sizeof(k),k6);

		const char*fmt=
			"GET /socket.io/?EIO=3&transport=websocket HTTP/1.1\r\n"
			"Host: %s:%d\r\n"
			"Connection: Upgrade\r\n"
			"Pragma: no-cache\r\n"
			"Cache-Control: no-cache\r\n"
			"Upgrade: websocket\r\n"
			"Origin: http://%s:%d\r\n"
			"Sec-WebSocket-Version: 13\r\n"
			"Accept-Encoding: gzip, deflate\r\n"
			"Accept-Language: zh-CN,zh;q=0.9\r\n"
			"Sec-WebSocket-Key: %s\r\n"
			"Sec-WebSocket-Extensions: permessage-deflate; client_max_window_bits\r\n\r\n";

		if(connect_tcp(ip,port)<0)	
			return -1;

		char buf[512];
		int len=sprintf(buf,fmt,ip,port,ip,port,k6);

		if(len!=zio::writev(m_fd, buf, len))
		{
			close();
			return -1;
		}

		sleep(1);
		if(read_until("\r\n\r\n")<=0)
		{
			close();
			return -1;
		}

		if(m_buff.find("101 Switching Protocols\r\n"))
		{
			m_buff.skip(m_buff.find("\r\n\r\n"));
			return 0;
		}

		close();
		return -1;
	}

	int read_until(const char*what,int timeout=10*1000)
	{
		int rc=0;
		zclock c;
		for(;;)
		{
			buff_t b=m_buff.alloc();
			if(b.empty())
			{
				m_buff.grow(1024);
				b=m_buff.alloc();
			}

			int len=zio::read(m_fd,b.ptr(),b.len());

			if((int)c.count_ms()>timeout)
			{
				log_error("websocket read timeout in %dms",timeout);
				return -1;
			}

			if(len==-1)
			{
				return -1;
			}

			if(len==-2)
				continue;

			if(len==0)
			{
				log_info("remote close the socket");
				return 0;
			}

			m_buff.commit(len);
			rc+=len;

			if(m_buff.find(what))
				return rc;
		}
	}


	void close()
	{
		zio::close(m_fd);
	}

	~web_client_http()
	{
		close();
	}
};

int realpath_test()
{
	char buf[4096];

	realpath(".",buf);
	log_info("curpath=%s",buf);

	return 0;
}

#if 0
std::string test_json()
{
	struct json
	{
		json&put(const char*k,const json&n)
		{
		
			return *this;
		}
	
		json&put(const char*k,int v)
		{
		
			return *this;
		}
		json&put(const char*k,const char*v)
		{
		
			return *this;
		}

		std::string to_string()const
		{
			return std::string();
		}
	};

	return json().put("cmd","login")
				 .put("data",json()
				 	 .put("uname","wyj")
					 .put("passwd",111111)
				  ).to_string();
}
#endif

int main()
{
	unsigned char buf[32];
	const char*s64="puVOuWb7rel6z2AVZBKnfw==";

	int blen=32;
	base64::decode((char*)s64,strlen(s64),buf,blen);

	web_client_http client;
	client.connect_ws("60.220.238.150",8086);

	for(;;)
	{
		while(client.ws_parse()==0)
			client.ws_reset();

		client.read_until("\n");
	}

	return 0;
}


