#include "HTTPReader.h"
#include "..\Components\wac_network\wac_network_http_receiver_api.h"
#include "api__filereader.h"
#include "../nu/AutoChar.h"
#include <api/service/waservicefactory.h>
#include <api/filereader/api_readercallback.h>
#include <wchar.h>
#include <bfc/platform/strcmp.h>
#include <bfc/platform/minmax.h>
#ifdef _WIN32
#include <shlwapi.h>
#endif

#ifdef __APPLE__
#include <unistd.h>
#endif

#include <stdexcept>

// so we don't accidently call these CRT functions
#ifdef close
#undef close
#endif
#ifdef open
#undef open
#endif
#ifdef read
#undef read
#endif

#define config_guess_prebuffer true
#define config_buffer_size 64
#define config_prebuffer_size 24
#define config_prebuffer_min 0
#define config_allowseek true
#define config_fullseek true
#define config_seekprebuffer 1
#define config_suppressstatus false

// {C0A565DC-0CFE-405a-A27C-468B0C8A3A5C}
static const GUID internetConfigGroupGUID =
  {
    0xc0a565dc, 0xcfe, 0x405a, { 0xa2, 0x7c, 0x46, 0x8b, 0xc, 0x8a, 0x3a, 0x5c }
  };

class HttpReader
{
public:
	HttpReader(const char *url, uint64_t start_offset = 0, uint64_t total_len = 0, int is_seek = 0);
	~HttpReader();

	int connect();
	int read(int8_t *buffer, int length);
	
	void abort()                                                      { killswitch = 1; }
	
	int bytesAvailable();

	uint64_t getContentLength()
	{
		if (m_contentlength)
			return m_contentlength;

		return -1;
	}

	int canSeek()
	{
		return (m_contentlength &&
		        /* JF> this is correct but not as compatible: m_accept_ranges && */
		        !m_meta_interval);
	}

	uint64_t getPos()                                                 { return m_contentpos; }

	const char *getHeader( const char *header )                       { return httpGetter->getheader( (char *)header ); }

	void setMetaCB( api_readercallback *cb )                          { metacb = cb; }

	//static BOOL CALLBACK httpDlgProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam);

private:
	api_httpreceiver *httpGetter = NULL;
	api_dns          *dns        = NULL;

	char *m_AllHeaders;

	int buffer_size;
	int prebuffer_size, prebuffer_min;
	int need_prebuffer;
	uint64_t m_contentlength, m_contentpos;
	int m_accept_ranges;

	int proxy_enabled;
	char *proxy;

	int killswitch = -1;

	int m_meta_init, m_meta_interval, m_meta_pos, m_meta_size, m_meta_buf_pos;
	char m_meta_buf[4096];

	api_readercallback *metacb;

	int guessed_prebuffer_size;

	char lpinfo[256];
	char force_lpinfo[256];
	char *dlg_realm;
	char *m_url;
};


HttpReader::HttpReader( const char *url, uint64_t start_offset, uint64_t total_len, int is_seek )
{
	m_accept_ranges        = 0;
	buffer_size            = (config_buffer_size * 1024);
	prebuffer_size         = (config_prebuffer_size * 1024);
	prebuffer_min          = (config_prebuffer_min * 1024);
	guessed_prebuffer_size = !config_guess_prebuffer;

	if (is_seek)
	{
		prebuffer_min = prebuffer_size = config_seekprebuffer;
		guessed_prebuffer_size = 1;
	}

	proxy_enabled   = 0;
	killswitch      = 0;
	need_prebuffer  = 0;
	m_contentlength = total_len;
	m_contentpos    = start_offset;
	m_meta_init     = m_meta_interval = m_meta_pos = m_meta_size = m_meta_buf_pos = 0;
	m_meta_buf[0]   = 0;
	metacb          = NULL;
	force_lpinfo[0] = 0;
	lpinfo[0]       = 0;

	m_url           = _strdup(url);

	int use_proxy = 1;
	bool proxy80 = AGAVE_API_CONFIG->GetBool(internetConfigGroupGUID, L"proxy80", false);
	if (proxy80 && strstr(url, ":") && (!strstr(url, ":80/") && strstr(url, ":80") != (url + strlen(url) - 3)))
		use_proxy = 0;

	waServiceFactory *sf = WASABI_API_SVC->service_getServiceByGuid( httpreceiverGUID );
	if (sf)
		httpGetter = (api_httpreceiver *)sf->getInterface();

	const wchar_t *proxy = AGAVE_API_CONFIG->GetString(internetConfigGroupGUID, L"proxy", 0);

	httpGetter->open(API_DNS_AUTODNS, buffer_size, (use_proxy && proxy && proxy[0]) ? (char *)AutoChar(proxy) : NULL);
	httpGetter->addheader("Accept:*/*");
	if (!_strnicmp(url, "uvox://", 7))
	{
		httpGetter->addheader("User-Agent: ultravox/2.0");
	}
	else
	{
		httpGetter->AddHeaderValue("User-Agent", AutoChar(WASABI_API_APP->main_getVersionString()));
	}

	if (start_offset > 0)
	{
		char temp[128];
		sprintf(temp, "Range: bytes=%d-", (int)start_offset);
		httpGetter->addheader(temp);
	}
	else
		httpGetter->addheader("Icy-Metadata:1");

	httpGetter->connect((char *)m_url, start_offset > 0);
	HttpReader::connect();
	HttpReader::read(0, 0);

	//if (!config_suppressstatus) 		api->core_setCustomMsg(0, StringPrintf("[Connecting] %s",url));
}

HttpReader::~HttpReader()
{
	waServiceFactory *sf = WASABI_API_SVC->service_getServiceByGuid( httpreceiverGUID );
	if ( sf )
		sf->releaseInterface( httpGetter );
}

// TODO: BOOL CALLBACK HttpReader::httpDlgProc(HWND hwndDlg, UINT uMsg, WPARAM wParam,LPARAM lParam)

int HttpReader::connect()
{
	try
	{
		while ( killswitch >= 0 && httpGetter->run() == 0 && httpGetter->get_status() == 0 )
		{
#ifdef _WIN32
			//Sleep( 50 );
#else
			usleep( 50000 );
#endif
		}
		if ( killswitch )
			return 0;

		if ( httpGetter->get_status() == -1 )
		{
			int code = httpGetter->getreplycode();
			if ( code == 401 )
			{
				/* TODO:
					// authorization required
					JNL_Connection *m_connection=httpGetter->get_con();
					char str[4096];
					while (m_connection->recv_lines_available() > 0) {
						char *wwwa="WWW-Authenticate:";
						m_connection->recv_line(str,4096);
						if (!str[0]) break;
						if (!_strnicmp(str,wwwa,strlen(wwwa))) {
						char *s2="Basic realm=\"";
						char *p=str+strlen(wwwa); while (p && *p== ' ') p++;
						if (!_strnicmp(p,s2,strlen(s2))) {
							p+=strlen(s2);
							if (strstr(p,"\"")) {
							strstr(p,"\"")[0]=0;
							if (*p) {
								if(force_lpinfo[0]) {
								force_lpinfo[0]=0; // invalid l/p
								} else WASABI_API_CONFIG->getStringPrivate(StringPrintf("HTTP-AUTH/%s",p),force_lpinfo,sizeof(force_lpinfo),"");
								if (!force_lpinfo[0] || lpinfo[0]) {
								dlg_realm = p;
								api->pushModalWnd();
								RootWnd *pr=api->main_getRootWnd();
								while(pr->getParent()) pr=pr->getParent();
								if (!DialogBoxParam(the->gethInstance(),MAKEINTRESOURCE(IDD_HTTPAUTH),pr->gethWnd(),httpDlgProc,(long)this)) {
									force_lpinfo[0]=0;
								} else {
									WASABI_API_CONFIG->setStringPrivate(StringPrintf("HTTP-AUTH/%s",p),force_lpinfo);
								}
								api->popModalWnd();
								}
								if (force_lpinfo[0]) {
								const char *p=STRSTR(m_url,"http://");
								if(p) {
									p+=7;
									StringPrintf tmp("http://%s@%s",force_lpinfo,p);
									httpGetter->connect((char *)tmp.getValue());
									return connect(); // recursive city
								}
								}
							}
							}
						}
						break;
						}
					}*/
			}
			// TODO: api->core_setCustomMsg(0, StringPrintf("HTTP: can't connect (%i)",code));
			return 0;
		}

		if ( httpGetter->getreplycode() < 200 || httpGetter->getreplycode() > 299 )
		{
			// TODO: api->core_setCustomMsg(0, StringPrintf("HTTP: returned %i",httpGetter->getreplycode()));
			return 0;
		}

		need_prebuffer = 1;
	}
	catch ( const std::exception &e )
	{
		return 0;
	}


	return 1;
}

int HttpReader::bytesAvailable()
{
	int code = httpGetter->run();
	int ba = httpGetter->bytes_available();

	if ( !ba && code )
		return -1;

	return ba;
}

int HttpReader::read(int8_t *buffer, int length)
{
	if (!httpGetter->GetConnection())
		return 0;

	if ( httpGetter->GetConnection()->get_state() == CONNECTION_STATE_CONNECTED && httpGetter->bytes_available() < prebuffer_min )
		need_prebuffer = 1;

	if (need_prebuffer)
	{
		need_prebuffer = 0;
		// TODO: if (!config_suppressstatus) api->core_setCustomMsg(0, "Prebuffering ...");

		if (!guessed_prebuffer_size)
		{
			// wait for headers
			int s;
			do
			{
				s = httpGetter->run();
			}
			while (s == 0 && httpGetter->get_status() != 2);

			// calculate the needed prebuffer size if it's a shoutcast stream
			const char *icybr;
			if (icybr = httpGetter->getheader("icy-br"))
			{
				prebuffer_size = (atoi(icybr) / 8) * 4096;
				prebuffer_min = (atoi(icybr) / 8) * 1024;

				if (prebuffer_size > buffer_size)
					prebuffer_size = buffer_size;
			}

			guessed_prebuffer_size = 1;
		}

		int last_pre = -1;
		while (httpGetter->bytes_available() < prebuffer_size && !killswitch)
		{
			int s = httpGetter->run();
//      JNL_Connection::state s = getter->get_state();
			//    if (s == JNL_Connection::STATE_ERROR || s == JNL_Connection::STATE_CLOSED) break;
			if (s == -1 || s == 1) break;
#ifdef _WIN32
			Sleep(50);
#else
      usleep(50000);
#endif
			if (last_pre != httpGetter->bytes_available() && !killswitch)
			{
// TODO:      if (!config_suppressstatus) api->core_setCustomMsg(0, StringPrintf(0, "Prebuffering : %i/%i bytes",httpGetter->bytes_available(),prebuffer_size));
			}
		}

//		if (!killswitch)
//		{
//// TODO:      if (!config_suppressstatus) api->core_setCustomMsg(0, "Prebuffering done.");
//		}
	}

	if (killswitch) return 0;

	// metadata filtering
	if ( !m_meta_init )
	{
		const char *v;
		if ( v = httpGetter->getheader( "icy-metaint:" ) )
			m_meta_interval = atoi( v );

		if ( !m_contentlength )
		{
			if ( v = httpGetter->getheader( "content-length:" ) )
				m_contentlength = _strtoui64( v, NULL, 10 );//atoi(v);
		}

		v = httpGetter->getheader( "accept-ranges:" );
		if ( v && strcasestr( v, "bytes" ) )
			m_accept_ranges = 1;

		m_meta_init = 1;
	}

	int error = 0, recvBytes = 0;
	while (length && !error && !killswitch)
	{
		int code = httpGetter->run();

		if (code != 0)
			error = 1;

		// old metadata parsing
		/*if (httpGetter->bytes_available()>0) {
		  int l=httpGetter->get_bytes(buffer,length);

		  // metadata stuff
		if (m_meta_interval) {
		int x=l;
		unsigned char *buf=(unsigned char *)buffer;
		if (m_meta_size)// already in meta block
		{				
			int len=MIN(x,m_meta_size-m_meta_buf_pos);

			MEMCPY(m_meta_buf+m_meta_buf_pos,buf,len);
			m_meta_buf_pos+=len;

			if (m_meta_buf_pos==m_meta_size)
			{
				if(metacb) metacb->metaDataReader_onData(m_meta_buf,m_meta_size);
				m_meta_buf_pos=0;
				m_meta_size=0;
				m_meta_pos=0;
			}

			x-=len;
			if (x) MEMCPY(buf,buf+len,x);
		}
		else if (m_meta_pos+x > m_meta_interval) // block contains meta data somewhere in it, and we're not alreayd reading a block
		{
			int start_offs=m_meta_interval-m_meta_pos;
			int len;
			m_meta_size=((unsigned char *)buf)[start_offs]*16;

			len=MIN(x-start_offs-1,m_meta_size);

			if (len) MEMCPY(m_meta_buf,buf+start_offs+1,len);
			m_meta_buf_pos=len;

			if (m_meta_buf_pos==m_meta_size) // full read of metadata successful
			{
				x-=m_meta_size+1;
				if (x > start_offs) MEMCPY(buf+start_offs,buf+start_offs+1+m_meta_size,x-start_offs);
				if(metacb) metacb->metaDataReader_onData(m_meta_buf,m_meta_size);
				m_meta_buf_pos=0;
				m_meta_pos=-start_offs;
				m_meta_size=0;
			}
			else 
			{
				x=start_offs; // otherwise, there's only the first block of data
			}
		}
		if (x > 0) 
		{
			m_meta_pos+=x;
		}
		l=x;
		}

		  length-=l;
		  buffer+=l;
		  recvBytes+=l;
		} else Sleep(50);*/

		while (1)
		{
			int len = httpGetter->bytes_available();
			if (m_meta_interval && m_meta_pos >= m_meta_interval)
			{
				unsigned char b;
				if (len > 0 && httpGetter->peek_bytes((char*)&b, 1) && len > (b << 4))
				{
					char metabuf[4096];
					httpGetter->get_bytes(metabuf, 1);
					httpGetter->get_bytes(metabuf, b << 4);
					if (metacb) metacb->metaDataReader_onData(metabuf, b << 4);
					//stream_metabytes_read+=(b<<4)+1;
					m_meta_pos = 0;
				}
				else
					break;
			}
			else
			{
				len = MIN(length, len);
				if (m_meta_interval)
					len = MIN(m_meta_interval - m_meta_pos, len);

				if (len > 0)
				{
					len = httpGetter->get_bytes((char*)buffer, len);
					m_meta_pos += len;
					//stream_bytes_read+=len;
					length -= len;
					buffer += len;
					recvBytes += len;
				}
				else
				{
#ifdef _WIN32
          Sleep(50);
#else
          usleep(50000);
#endif
				}
				break;
			}
		}

		/*    int s=httpGetter->get_con()->get_state();
		    if(code==0) {*/
		/*      char tmp[512];
		      wsprintf(tmp,"[Connected] Retrieving list (%i bytes)", recvBytes);
		      api->status_setText(tmp);*/
//    } else error=1;
	}

	m_contentpos += recvBytes;
	
	return recvBytes;
}


/* ---------------------------------------------------------------------- */
int HTTPReader::isMine(const wchar_t *filename, int mode)
{
	if (!_wcsnicmp(filename, L"http://", 7) ||
	    !_wcsnicmp(filename, L"https://", 8) ||
	    !_wcsnicmp(filename, L"icy://", 6) ||
	    !_wcsnicmp(filename, L"sc://", 5) ||
	    !_wcsnicmp(filename, L"uvox://", 7)) return 1;
	return 0;
}

int HTTPReader::open( const wchar_t *filename, int mode )
{
	if ( !isMine( filename, mode ) )
		return 0;

	m_filename = _strdup( AutoChar( filename ) );
	reader     = new HttpReader( m_filename );

	return 1;
}

uint64_t HTTPReader::bytesAvailable( uint64_t requested )
{
	int v = reader ? reader->bytesAvailable() : 0;
	if ( v > requested )
		return requested;

	return v;
}

size_t HTTPReader::read( int8_t *buffer, size_t length )
{
	if ( !reader )
		return 0;

	if ( !hasConnected )
	{
		int res = reader->connect();
		if ( !res )
			return 0;

		hasConnected = 1;
	}

	return reader->read( buffer, (int)length );
}

void HTTPReader::close()
{
	delete reader;
	reader = NULL;
}

void HTTPReader::abort()
{
	if ( reader )
		reader->abort();
}

uint64_t HTTPReader::getLength()
{
	return reader ? reader->getContentLength() : -1;
}

uint64_t HTTPReader::getPos()
{
	return reader ? reader->getPos() : 0;
}

int HTTPReader::canSeek()
{
	return ( config_allowseek && reader && reader->canSeek() ) ? ( config_fullseek ? 1 : -1 ) : 0;
}

int HTTPReader::seek( uint64_t position )
{
	if ( reader && reader->canSeek() && config_allowseek )
	{
		if ( position == getPos() ) return 0;
		hasConnected = 0;
		uint64_t cl = reader->getContentLength();
		delete( (HttpReader *)reader );
		reader = new HttpReader( m_filename, position, cl, 1 );
		return 0;
	}

	return -1;
}

int HTTPReader::hasHeaders()
{
	return 1;
}

const char *HTTPReader::getHeader( const char *header )
{
	return reader ? reader->getHeader( header ) : NULL;
}

void HTTPReader::setMetaDataCallback( api_readercallback *cb )
{
	if ( reader )
		reader->setMetaCB( cb );
}

#define CBCLASS HTTPReader
START_DISPATCH;
CB(ISMINE, isMine);
CB(OPEN, open);
CB(READ, read);
CB(WRITE, write);
VCB(CLOSE, close);
VCB(ABORT, abort);
CB(GETLENGTH, getLength);
CB(GETPOS, getPos);
CB(CANSEEK, canSeek);
CB(SEEK, seek);
CB(HASHEADERS, hasHeaders);
CB(GETHEADER, getHeader);
CB(EXISTS, exists);
//  CB(REMOVE,remove);
//  CB(REMOVEUNDOABLE,removeUndoable);
//  CB(MOVE,move);
CB(BYTESAVAILABLE, bytesAvailable);
VCB(SETMETADATACALLBACK, setMetaDataCallback);
CB(CANPREFETCH, canPrefetch);
//  CB(CANSETEOF, canSetEOF);
//  CB(SETEOF, setEOF);
END_DISPATCH;
#undef CBCLASS