#ifndef __MODULE_HTTP_H__
#define __MODULE_HTTP_H__
#include <ascript.h>

AScript_BeginModule(net_http)

class Header;
class Stream_Http;
class Object_Server;
class Object_Client;
class Object_Connection;

struct KeyCompare_Dict {
	inline bool operator()(const String &s1, const String &s2) const {
		return ::strcasecmp(s1.c_str(), s2.c_str()) < 0;
	}
};

//-----------------------------------------------------------------------------
// Chunk declaration
//-----------------------------------------------------------------------------
class Chunk {
public:
	enum Stat {
		STAT_Start,
		STAT_Size, STAT_ExtName, STAT_ExtValue,
		STAT_Complete,
	};
private:
	Stat _stat;
	size_t _size;
	String _extName;
	String _extValue;
public:
	inline Chunk() : _stat(STAT_Start), _size(0) {}
	~Chunk();
	inline bool IsComplete() const { return _stat == STAT_Complete; }
	inline size_t GetSize() const { return _size; }
	inline const char *GetExtName() const { return _extName.c_str(); }
	inline const char *GetExtValue() const { return _extValue.c_str(); }
	bool ParseChar(Signal sig, char ch);
};

//-----------------------------------------------------------------------------
// SimpleHTMLParser declaration
//-----------------------------------------------------------------------------
class SimpleHTMLParser {
public:
	enum Stat {
		STAT_Init,
		STAT_Tag1stChar,
		STAT_TagName, STAT_SeekTagClose,
		STAT_AttrName, STAT_SeekEqual,
		STAT_AttrValue,
		STAT_AttrValueQuotedS, STAT_AttrValueQuotedD, STAT_AttrValueNaked,
		STAT_Comment, STAT_Comment1stChar, STAT_Comment2ndChar,
		STAT_SkipSpace,
		STAT_Error, STAT_Complete,
	};
	typedef std::map<String, String, KeyCompare_Dict> Attrs;
protected:
	Stat _stat;
	Stat _statNext;
	char _tagPrefix;
	String _tagName;
	String _attrName, _attrValue;
	Attrs _attrs;
public:
	inline SimpleHTMLParser() :
			_stat(STAT_Complete), _statNext(STAT_Complete), _tagPrefix('\0') {}
	bool ParseChar(Signal sig, char ch);
	inline void Activate() { _stat = _statNext = STAT_Init; }
	inline bool IsComplete() const {
		return _stat == STAT_Complete;
	}
	inline bool IsActive() const {
		return _stat != STAT_Error && _stat != STAT_Complete;
	}
	virtual bool AcceptTag(Signal sig,
				char tagPrefix, const char *tagName, const Attrs &attrs) = 0;
};

//-----------------------------------------------------------------------------
// EncodingDetector declaration
//-----------------------------------------------------------------------------
class EncodingDetector : public SimpleHTMLParser {
private:
	String _encoding;
public:
	virtual bool AcceptTag(Signal sig,
				char tagPrefix, const char *tagName, const Attrs &attrs);
	inline bool IsValidEncoding() const {
		return IsComplete() && !_encoding.empty();
	}
	inline const char *GetEncoding() const { return _encoding.c_str(); }
};

//-----------------------------------------------------------------------------
// LinkDetector declaration
//-----------------------------------------------------------------------------
class LinkDetector : public SimpleHTMLParser {
public:
	virtual bool AcceptTag(Signal sig,
				char tagPrefix, const char *tagName, const Attrs &attrs);
};

//-----------------------------------------------------------------------------
// ContentType declaration
//-----------------------------------------------------------------------------
class ContentType {
private:
	String _type;
	String _charset;
public:
	inline ContentType() {}
	bool Parse(Signal sig, const char *str);
	inline const char *GetType() const { return _type.c_str(); }
	inline const char *GetCharset() const { return _charset.c_str(); }
	inline bool IsValidCharset() const { return !_charset.empty(); }
};

//-----------------------------------------------------------------------------
// Header declaration
//-----------------------------------------------------------------------------
class Header {
public:
	enum Stat {
		STAT_LineTop, STAT_FieldName, STAT_FieldValue,
		STAT_LineFolded, STAT_SkipWhiteSpace,
		STAT_Complete,
	};
	typedef std::map<String, StringList *, KeyCompare_Dict> Dict;
private:
	Stat _stat;
	String _fieldName;
	String _fieldValue;
	Dict _dict;
public:
	inline Header(bool completeFlag = false) :
					_stat(completeFlag? STAT_Complete : STAT_LineTop) {}
	Header(const Header &header);
	~Header();
	bool ParseChar(Signal sig, char ch);
	bool IsComplete() const { return _stat == STAT_Complete; }
	void Reset();
	bool SetFields(Signal sig, const ValueDict &valueDict, Stream *pStreamBody);
	Value GetFieldsAsDict(Environment &env) const;
	void SetField(const char *fieldName, const char *fieldValue);
	bool GetField(const char *fieldName, StringList **ppStringList) const;
	bool GetField(Environment &env, Signal sig, const Symbol *pSymbol, Value &value) const;
	bool IsField(const char *fieldName, const char *value, bool *pFoundFlag = NULL) const;
	inline static void SetError_InvalidFormat(Signal sig) {
		sig.SetError(ERR_FormatError, "invalid format of HTTP header");
	}
	String GetString() const;
	Stream_Http *GenerateUpStream(Signal sig,
					Object *pObjOwner, int sock, const char *name) const;
	Stream_Http *GenerateDownStream(Signal sig,
					Object *pObjOwner, int sock, const char *name) const;
};

//-----------------------------------------------------------------------------
// Request declaration
//-----------------------------------------------------------------------------
class Request {
public:
	enum Stat {
		STAT_Start,
		STAT_Method,
		STAT_RequestURI,
		STAT_HttpVersion,
		STAT_SkipSpace, STAT_Header,
	};
private:
	Stat _stat;
	Stat _statNext;
	String _method;			// method
	String _requestURI;		// uri
	String _httpVersion;	// version
	Header _header;
public:
	inline Request() : _stat(STAT_Start), _statNext(STAT_Start) {}
	inline void SetRequest(const char *method, const char *requestURI, const char *httpVersion) {
		_method = method, _requestURI = requestURI, _httpVersion = httpVersion;
		_header.Reset();
	}
	bool ParseChar(Signal sig, char ch);
	bool IsComplete() const { return _header.IsComplete(); }
	inline Header &GetHeader() { return _header; }
	inline const Header &GetHeader() const { return _header; }
	inline void Reset() {
		_stat = STAT_Start, _statNext = STAT_Start;
		_method.clear(), _requestURI.clear(), _httpVersion.clear();
		_header.Reset();
	}
	inline const char *GetMethod() const { return _method.c_str(); }
	inline const char *GetRequestURI() const { return _requestURI.c_str(); }
	inline const char *GetHttpVersion() const { return _httpVersion.c_str(); }
	inline static void SetError_InvalidFormat(Signal sig) {
		sig.SetError(ERR_FormatError, "invalid format of HTTP request");
	}
	inline bool HasBody() const {
		// see RFC 2616 4.3 MessageBody
		return _header.GetField("Content-Length", NULL) ||
							_header.GetField("Transfer-Encoding", NULL);
	}
	bool Send(Signal sig, int sock);
	bool Receive(Signal sig, int sock);
};

//-----------------------------------------------------------------------------
// Status declaration
//-----------------------------------------------------------------------------
class Status {
public:
	enum Stat {
		STAT_Start,
		STAT_HttpVersion, STAT_StatusCode, STAT_ReasonPhrase,
		STAT_SkipSpace, STAT_Header,
	};
	enum Code {
		CODE_Invalid,
		// RFC 2616 10.1 Informational 1xx
		CODE_Continue,
		CODE_SwitchingProtocols,
		// RFC 2616 10.2 Successful 2xx
		CODE_OK,
		CODE_Created,
		CODE_Accepted,
		CODE_NonAuthoritativeInformation,
		CODE_NoContent,
		CODE_ResetContent,
		CODE_PartialContent,
		// RFC 2616 10.3 Redirection 3xx
		CODE_MultipleChoices,
		CODE_MovedPermanently,
		CODE_Found,
		CODE_SeeOther,
		CODE_NotModified,
		CODE_UseProxy,
		CODE_TemporaryRedirect,
		// RFC 2616 10.4 Client Error 4xx
		CODE_BadRequest,
		CODE_Unauthorized,
		CODE_PaymentRequired,
		CODE_Forbidden,
		CODE_NotFound,
		CODE_MethodNotAllowed,
		CODE_NotAcceptable,
		CODE_ProxyAuthenticationRequired,
		CODE_RequestTimeOut,
		CODE_Conflict,
		CODE_Gone,
		CODE_LengthRequired,
		CODE_PreconditionFailed,
		CODE_RequestEntityTooLarge,
		CODE_RequestURITooLarge,
		CODE_UnsupportedMediaType,
		CODE_RequestedRangeNotSatisfiable,
		CODE_ExpectationFailed,
		// RFC 2616 10.5 Server Error 5xx
		CODE_InternalServerError,
		CODE_NotImplemented,
		CODE_BadGateway,
		CODE_ServiceUnavailable,
		CODE_GatewayTimeOut,
		CODE_HTTPVersionNotSupported,
	};
	struct CodePhrase {
		const char *statusCode;
		const char *reasonPhrase;
		Code code;
	};
private:
	Stat _stat;
	Stat _statNext;
	String _httpVersion;	// version
	String _statusCode;		// code
	String _reasonPhrase;	// reason
	Header _header;
	static const CodePhrase _codePhraseTbl[];
public:
	inline Status() : _stat(STAT_Start), _statNext(STAT_Start) {}
	inline Status(const Status &status) : _stat(status._stat), _statNext(status._statNext),
		_httpVersion(status._httpVersion), _statusCode(status._statusCode),
		_reasonPhrase(status._reasonPhrase), _header(status._header) {}
	void SetStatus(const char *httpVersion, const char *statusCode, const char *reasonPhrase);
	bool ParseChar(Signal sig, char ch);
	bool IsComplete() const { return _header.IsComplete(); }
	inline Header &GetHeader() { return _header; }
	inline const Header &GetHeader() const { return _header; }
	inline void Reset() {
		_stat = STAT_Start, _statNext = STAT_Start;
		_httpVersion.clear(), _statusCode.clear(), _reasonPhrase.clear();
		_header.Reset();
	}
	inline const char *GetHttpVersion() const { return _httpVersion.c_str(); }
	inline const char *GetStatusCode() const { return _statusCode.c_str(); }
	inline const char *GetReasonPhrase() const { return _reasonPhrase.c_str(); }
	inline bool IsOK() const {
		return _statusCode.compare("200") == 0;
	}
	inline static void SetError_InvalidFormat(Signal sig) {
		sig.SetError(ERR_FormatError, "invalid format of HTTP status");
	}
	inline bool HasBody() const {
		// see RFC 2616 4.3 MessageBody
		return _statusCode.compare("204") != 0 && _statusCode.compare("304") != 0;
	}
	bool Send(Signal sig, int sock);
	bool Receive(Signal sig, int sock);
};

//-----------------------------------------------------------------------------
// Stream_Socket declaration
//-----------------------------------------------------------------------------
class Stream_Socket : public Stream {
private:
	Object *_pObjOwner;
	int _sock;
public:
	Stream_Socket(Signal sig, Object *pObjOwner, int sock);
	~Stream_Socket();
	virtual const char *GetName() const;
	virtual size_t DoRead(Signal sig, void *buff, size_t bytes);
	virtual size_t DoWrite(Signal sig, const void *buff, size_t bytes);
	virtual bool DoSeek(Signal sig, long offset, size_t offsetPrev, SeekMode seekMode);
	virtual bool DoFlush(Signal sig);
	virtual bool DoClose(Signal sig);
	virtual size_t DoGetSize();
	virtual Object *DoGetStatObj(Signal sig);
};

//-----------------------------------------------------------------------------
// Stream_Chunked declaration
//-----------------------------------------------------------------------------
class Stream_Chunked : public Stream {
private:
	Stream *_pStream;
	size_t _bytesChunk;
	bool _doneFlag;
	OAL::Memory _memory;
public:
	Stream_Chunked(Signal sig, Stream *pStream, unsigned long attr);
	~Stream_Chunked();
	virtual const char *GetName() const;
	virtual size_t DoRead(Signal sig, void *buff, size_t bytes);
	virtual size_t DoWrite(Signal sig, const void *buff, size_t bytes);
	virtual bool DoSeek(Signal sig, long offset, size_t offsetPrev, SeekMode seekMode);
	virtual bool DoFlush(Signal sig);
	virtual bool DoClose(Signal sig);
	virtual size_t DoGetSize();
};

//-----------------------------------------------------------------------------
// Stream_Http declaration
//-----------------------------------------------------------------------------
class Stream_Http : public Stream {
private:
	Stream *_pStream;
	String _name;
	size_t _bytesRead;
	Header _header;
	EncodingDetector _encodingDetector;
	OAL::Memory _memory;
public:
	Stream_Http(Signal sig, Stream *pStream, unsigned long attr,
						const char *name, size_t bytes, const Header &header);
	~Stream_Http();
	virtual const char *GetName() const;
	virtual size_t DoRead(Signal sig, void *buff, size_t bytes);
	virtual size_t DoWrite(Signal sig, const void *buff, size_t bytes);
	virtual bool DoSeek(Signal sig, long offset, size_t offsetPrev, SeekMode seekMode);
	virtual bool DoFlush(Signal sig);
	virtual bool DoClose(Signal sig);
	virtual size_t DoGetSize();
	virtual Object *DoGetStatObj(Signal sig);
	inline void ActivateEncodingDetector() {
		_encodingDetector.Activate();
	}
	bool Cleanup(Signal sig);
};

//-----------------------------------------------------------------------------
// Object_Stat
//-----------------------------------------------------------------------------
AScript_DeclarePrivClass(Stat);

class Object_Stat : public Object {
private:
	Header _header;
public:
	AScript_DeclareObjectAccessor(Stat)
public:
	inline Object_Stat(const Header &header) :
				Object(AScript_PrivClass(Stat)), _header(header) {}
	inline Object_Stat(const Object_Stat &obj) :
				Object(obj), _header(obj._header) {}
	virtual ~Object_Stat();
	virtual Object *Clone() const;
	virtual Value DoPropGet(Signal sig, const Symbol *pSymbol, bool &evaluatedFlag);
	virtual String ToString(Signal sig, bool exprFlag);
};

//-----------------------------------------------------------------------------
// Object_Request declaration
//-----------------------------------------------------------------------------
AScript_DeclarePrivClass(Request);

class Object_Request : public Object {
public:
	AScript_DeclareObjectAccessor(Request)
private:
	Object_Connection *_pObjConnection;
	Status _status;
public:
	inline Object_Request(Object_Connection *pObjConnection) :
			Object(AScript_PrivClass(Request)), _pObjConnection(pObjConnection) {}
	virtual ~Object_Request();
	virtual Value DoPropGet(Signal sig, const Symbol *pSymbol, bool &evaluatedFlag);
	virtual Object *Clone() const;
	virtual String ToString(Signal sig, bool exprFlag);
	bool SendResponse(Signal sig,
		const char *statusCode, Stream *pStreamBody, const char *reasonPhrase,
		const char *httpVersion, const ValueDict &valueDict);
	Stream *SendRespChunk(Signal sig,
		const char *statusCode, const char *reasonPhrase,
		const char *httpVersion, const ValueDict &valueDict);
	inline Object_Connection *GetConnectionObj() { return _pObjConnection; }
};

//-----------------------------------------------------------------------------
// Object_Response declaration
//-----------------------------------------------------------------------------
AScript_DeclarePrivClass(Response);

class Object_Response : public Object {
public:
	AScript_DeclareObjectAccessor(Response)
private:
	Object_Client *_pObjClient;
public:
	inline Object_Response(Object_Client *pObjClient) :
			Object(AScript_PrivClass(Response)), _pObjClient(pObjClient) {}
	virtual ~Object_Response();
	virtual Value DoPropGet(Signal sig, const Symbol *pSymbol, bool &evaluatedFlag);
	virtual Object *Clone() const;
	virtual String ToString(Signal sig, bool exprFlag);
	//Stream *GenerateStream(Signal sig);
};

//-----------------------------------------------------------------------------
// Object_Connection declaration
//-----------------------------------------------------------------------------
AScript_DeclarePrivClass(Connection);

class Object_Connection : public Object {
public:
	AScript_DeclareObjectAccessor(Connection)
private:
	Object_Server *_pObjServer;
	Stream_Http *_pStreamHttp;
	int _sock;
	String _remoteIP;
	String _remoteHost;
	String _remoteLogname;
	String _localIP;
	String _localHost;
	Request _request;
public:
	inline Object_Connection(Object_Server *pObjServer, int sock,
			const char *remoteIP, const char *remoteHost, const char *remoteLogname,
			const char *localIP, const char *localHost) :
		Object(AScript_PrivClass(Connection)),
		_pObjServer(pObjServer), _pStreamHttp(NULL), _sock(sock),
		_remoteIP(remoteIP), _remoteHost(remoteHost), _remoteLogname(remoteLogname),
		_localIP(localIP), _localHost(localHost) {}
	inline Object_Connection(const Object_Connection &obj) : Object(obj) {}
	virtual ~Object_Connection();
	virtual Value DoPropGet(Signal sig, const Symbol *pSymbol, bool &evaluatedFlag);
	virtual Object *Clone() const;
	virtual String ToString(Signal sig, bool exprFlag);
	inline bool IsValid() const { return _sock > 0; }
	inline int GetSocket() const { return _sock; }
	inline Request &GetRequest() { return _request; }
	inline Stream_Http *GetStream() { return _pStreamHttp; }
	bool ReceiveRequest(Signal sig);
	bool CleanupRequest(Signal sig);
};

//-----------------------------------------------------------------------------
// Object_Server declaration
//-----------------------------------------------------------------------------
AScript_DeclarePrivClass(Server);

class Object_Server : public Object {
public:
	typedef std::vector<Object_Connection *> ConnectionList;
public:
	AScript_DeclareObjectAccessor(Server)
private:
	String _addr;
	short _port;
	int _sockListen;
	fd_set _fdsRead;
	ConnectionList _connectionList;
public:
	Object_Server();
	virtual ~Object_Server();
	virtual Object *Clone() const;
	virtual String ToString(Signal sig, bool exprFlag);
	bool Prepare(Signal sig, const char *addr, short port);
	Object_Request *Wait(Signal sig);
};

//-----------------------------------------------------------------------------
// Object_Client declaration
//-----------------------------------------------------------------------------
AScript_DeclarePrivClass(Client);

class Object_Client : public Object {
public:
	AScript_DeclareObjectAccessor(Client)
private:
	Stream_Http *_pStreamHttp;
	int _sock;
	String _addr;
	String _addrProxy;
	short _port;
	short _portProxy;
	String _userIdProxy;
	String _passwordProxy;
	Request _request;
	Status _status;
public:
	inline Object_Client() : Object(AScript_PrivClass(Client)),
			_pStreamHttp(NULL), _sock(-1), _port(0), _portProxy(0) {}
	virtual ~Object_Client();
	virtual Object *Clone() const;
	virtual String ToString(Signal sig, bool exprFlag);
	bool Prepare(Signal sig, const char *addr, short port,
					const char *addrProxy, short portProxy,
					const char *userIdProxy, const char *passwordProxy);
	inline Request &GetRequest() { return _request; }
	inline Status &GetStatus() { return _status; }
	inline Stream_Http *GetStream() { return _pStreamHttp; }
	inline bool IsViaProxy() const { return !_addrProxy.empty(); }
	Object_Response *SendRequest(Signal sig,
			const char *method, const char *uri, Stream *pStreamBody,
			const char *httpVersion, const ValueDict &valueDict);
	bool CleanupResponse(Signal sig);
};

//-----------------------------------------------------------------------------
// Object_Proxy declaration
//-----------------------------------------------------------------------------
AScript_DeclarePrivClass(Proxy);

class Object_Proxy : public Object {
public:
	AScript_DeclareObjectAccessor(Proxy)
private:
	String _addr;
	short _port;
	String _userId;
	String _password;
	Function *_pFuncCriteria;
public:
	inline Object_Proxy(const char *addr, short port,
					const char *userId, const char *password, Function *pFuncCriteria) :
		Object(AScript_PrivClass(Proxy)), _addr(addr), _port(port),
		_userId(userId), _password(password), _pFuncCriteria(pFuncCriteria) {}
	virtual ~Object_Proxy();
	virtual Object *Clone() const;
	virtual String ToString(Signal sig, bool exprFlag);
	inline const char *GetAddr() const { return _addr.c_str(); }
	inline short GetPort() const { return _port; }
	inline const char *GetUserId() const { return _userId.c_str(); }
	inline const char *GetPassword() const { return _password.c_str(); }
	bool IsResponsible(Environment &env, Signal sig, const char *addr) const;
};

//-----------------------------------------------------------------------------
// Directory_Http declaration
//-----------------------------------------------------------------------------
class Directory_Http : public Directory {
private:
	String _scheme;
	String _authority;
	String _query;
	String _fragment;
public:
	Directory_Http(Directory *pParent, const char *name, Type type);
	virtual ~Directory_Http();
	virtual Directory *DoNext(Environment &env, Signal sig);
	virtual Stream *DoOpenStream(Environment &env, Signal sig,
									unsigned long attr, const char *encoding);
	inline void SetScheme(const char *scheme) { _scheme = scheme; }
	inline void SetAuthority(const char *authority) { _authority = authority; }
	inline void SetQuery(const char *query) { _query = query; }
	inline void SetFragment(const char *fragment) { _fragment = fragment; }
	inline const char *GetScheme() const { return _scheme.c_str(); }
	inline const char *GetAuthority() const { return _authority.c_str(); }
	inline const char *GetQuery() const { return _query.c_str(); }
	inline const char *GetFragment() const { return _fragment.c_str(); }
};

//-----------------------------------------------------------------------------
// DirectoryFactory_Http declaration
//-----------------------------------------------------------------------------
class DirectoryFactory_Http : public DirectoryFactory {
public:
	virtual bool IsResponsible(Environment &env, Signal sig,
					const Directory *pParent, const char *pathName);
	virtual Directory *DoOpenDirectory(Environment &env, Signal sig,
		Directory *pParent, const char **pPathName, Directory::NotFoundMode notFoundMode);
};

}}

#endif
