/****************************************************************************************************

RepRapFirmware - Webserver

This class serves a single-page web applications to the attached network.  This page forms the user's 
interface with the RepRap machine.  This software interprests returned values from the page and uses it
to Generate G Codes, which it sends to the RepRap.  It also collects values from the RepRap like 
temperature and uses those to construct the web page.

The page itself - reprap.htm - uses Knockout.js and Jquery.js.  See:

http://knockoutjs.com/

http://jquery.com/

-----------------------------------------------------------------------------------------------------

Version 0.2

10 May 2013

Adrian Bowyer
RepRap Professional Ltd
http://reprappro.com

Licence: GPL

****************************************************************************************************/

#ifndef WEBSERVER_H
#define WEBSERVER_H

const unsigned int gcodeBufferLength = 512;		// size of our gcode ring buffer, preferably a power of 2
const unsigned int gcodeReplyBufferLength = 2048;	// size of our gcode reply buffer

/* HTTP */

#define KO_START "rr_"
#define KO_FIRST 3

const unsigned int webUploadBufferSize = 2300;	// maximum size of HTTP upload packets (webMessageLength - 700)
const unsigned int webMessageLength = 3000;		// maximum length of the web message we accept after decoding, excluding POST data

const unsigned int maxCommandWords = 4;			// max number of space-separated words in the command
const unsigned int maxQualKeys = 5;				// max number of key/value pairs in the qualifier
const unsigned int maxHeaders = 10;				// max number of key/value pairs in the headers

const unsigned int jsonReplyLength = 2000;		// size of buffer used to hold JSON reply

/* FTP */

const unsigned int ftpMessageLength = 256;		// maximum line length for incoming FTP commands
const unsigned int ftpResponseLength = 256;		// maximum FTP response length

/* Telnet */

const unsigned int telnetMessageLength = 256;	// maximum line length for incoming Telnet commands


class Webserver;

// Class to hold Gcode file information
class GcodeFileInfo
{
public:
	unsigned long fileSize;
	float objectHeight;
	float filamentNeeded[DRIVES - AXES];
	unsigned int numFilaments;
	float layerHeight;
	char generatedBy[50];
};

// This is the abstract class for all supported protocols
// Any inherited class should implement a state machine to increase performance and reduce memory usage.
class ProtocolInterpreter
{
	public:

		ProtocolInterpreter(Platform *p, Webserver *ws);
		virtual void ConnectionEstablished() { }
		virtual void ConnectionLost(uint16_t local_port) { }
		virtual bool CharFromClient(const char c) = 0;
		virtual void ResetState() = 0;

	    virtual bool StoreUploadData(const char* data, unsigned int len);
	    bool FlushUploadData();
	    void CancelUpload();
		bool IsUploading() const { return uploadState != notUploading; }

		virtual bool DebugEnabled() const;

		virtual ~ProtocolInterpreter() { }					// to keep Eclipse happy

	protected:

	    bool gotPassword;
	    Platform *platform;
	    Webserver *webserver;

	    // Information for file uploading
	    enum UploadState
	    {
			notUploading,									// no upload in progress
			uploadOK,										// upload in progress, no error so far
			uploadError										// upload in progress but had error
	    };

	    UploadState uploadState;
	    FileData fileBeingUploaded;
	    char filenameBeingUploaded[MaxFilenameLength + 1];
	    const char *uploadPointer;							// pointer to start of uploaded data not yet written to file
	    unsigned int uploadLength;							// amount of data not yet written to file
	    uint32_t numContinuationBytes;						// number of UTF-8 continuation bytes we have received

	    bool StartUpload(FileStore *file);
	    void FinishUpload(uint32_t file_length);
};

class Webserver
{   
  public:

    Webserver(Platform* p);
    void Init();
    void Spin();
    void Exit();
    void Diagnostics();

    void SetPassword(const char* pw);
    void SetName(const char* nm);
    const char *GetName() const;
    bool CheckPassword(const char* pw) const;

    bool GCodeAvailable();
    char ReadGCode();

    void ConnectionLost(const ConnectionState *cs);
    void ConnectionError();
    const StringRef& GetGcodeReply() const { return gcodeReply; }
    unsigned int GetReplySeq() const { return seq; }
    unsigned int GetGcodeBufferSpace() const;

    static bool GetFileInfo(const char *directory, const char *fileName, GcodeFileInfo& info);

    friend class Platform;

  protected:

    void MessageStringToWebInterface(const char *s, bool error);
    void AppendReplyToWebInterface(const char* s, bool error);

  private:

	class HttpInterpreter : public ProtocolInterpreter
	{
		public:

			HttpInterpreter(Platform *p, Webserver *ws);

			bool CharFromClient(const char c);
			void ResetState();

		private:

			// HTTP server state enumeration. The order is important, in particular xxxEsc1 must follow xxx, and xxxEsc2 must follow xxxEsc1.
			// We assume that qualifier keys do not contain escapes, because none of ours needs to be encoded. If we are sent escapes in the key,
			// it won't do any harm, but the key won't be recognised even if it would be valid were it decoded.
			enum HttpState
			{
				doingCommandWord,			// receiving a word in the first line of the HTTP request
				doingFilename,				// receiving the filename (second word in the command line)
				doingFilenameEsc1,			// received '%' in the filename (e.g. we are being asked for a filename with spaces in it)
				doingFilenameEsc2,			// received '%' and one hex digit in the filename
				doingQualifierKey,			// receiving a key name in the HTTP request
				doingQualifierValue,		// receiving a key value in the HTTP request
				doingQualifierValueEsc1,	// received '%' in the qualifier
				doingQualifierValueEsc2,	// received '%' and one hex digit in the qualifier
				doingHeaderKey,				// receiving a header key
				expectingHeaderValue,		// expecting a header value
				doingHeaderValue,			// receiving a header value
				doingHeaderContinuation,	// received a newline after a header value
				doingPost					// receiving post data
			};

			struct KeyValueIndices
			{
				const char* key;
				const char* value;
			};

			void SendFile(const char* nameOfFileToSend);
			void SendJsonResponse(const char* command);
			bool GetJsonResponse(const char* request, StringRef& response, const char* key, const char* value, size_t valueLength, bool& keepOpen);
			void GetJsonUploadResponse(StringRef& response);
			bool ProcessMessage();
			bool RejectMessage(const char* s, unsigned int code = 500);

			HttpState state;

//			bool receivingPost;
//			int boundaryCount;
//			FileStore* postFile;
//			bool postSeen;
//			bool getSeen;
//			bool clientLineIsBlank;

			// Buffers for processing HTTP input
			char clientMessage[webMessageLength];			// holds the command, qualifier, and headers
//			char postBoundary[postBoundaryLength];			// holds the POST boundary string
//			char postFileName[postFilenameLength];			// holds the POST filename
//			char postData[postDataLength];			    	// holds the POST data
			unsigned int clientPointer;						// current index into clientMessage

			const char* commandWords[maxCommandWords];
			KeyValueIndices qualifiers[maxQualKeys + 1];	// offsets into clientQualifier of the key/value pairs, the +1 is needed so that values can contain nulls
			KeyValueIndices headers[maxHeaders];			// offsets into clientHeader of the key/value pairs
			unsigned int numCommandWords;
			unsigned int numQualKeys;						// number of qualifier keys we have found, <= maxQualKeys
			unsigned int numHeaderKeys;						// number of keys we have found, <= maxHeaders

			// Buffers to hold reply
			char decodeChar;
	};
	HttpInterpreter *httpInterpreter;

	class FtpInterpreter : public ProtocolInterpreter
	{
		public:

			FtpInterpreter(Platform *p, Webserver *ws);
			void ConnectionEstablished();
			void ConnectionLost(uint16_t local_port);
			bool CharFromClient(const char c);
			void ResetState();

		private:

			enum FtpState
			{
				authenticating,			// not logged in
				authenticated,			// logged in
				waitingForPasvPort,		// waiting for connection to be established on PASV port
				pasvPortConnected,		// client connected to PASV port, ready to send data
				doingPasvIO				// client is connected and data is being transferred
			};
			FtpState state;

			char clientMessage[ftpMessageLength];
			unsigned int clientPointer;
			char ftpResponse[ftpResponseLength];

			char currentDir[MaxFilenameLength];
			char filename[MaxFilenameLength];

			float portOpenTime;

			void ProcessLine();
			void SendReply(int code, const char *message, bool keepConnection = true);
			void SendFeatures();

			void ReadFilename(int start);
			void ChangeDirectory(const char *newDirectory);
	};
	FtpInterpreter *ftpInterpreter;

	class TelnetInterpreter : public ProtocolInterpreter
	{
		public:

			TelnetInterpreter(Platform *p, Webserver *ws);
			void ConnectionEstablished();
			void ConnectionLost(uint16_t local_port);
			bool CharFromClient(const char c);
			void ResetState();

			void HandleGcodeReply(const char* reply, bool haveMore = false);

		private:

			enum TelnetState
			{
				authenticating,			// not logged in
				authenticated			// logged in
			};
			TelnetState state;

			char clientMessage[telnetMessageLength];
			unsigned int clientPointer;

			void ProcessLine();
	};
	TelnetInterpreter *telnetInterpreter;

	// G-Code processing
    void ProcessGcode(const char* gc);
    void LoadGcodeBuffer(const char* gc);
    void StoreGcodeData(const char* data, size_t len);

    // File info methods
    static bool FindHeight(const char* buf, size_t len, float& height);
    static bool FindLayerHeight(const char* buf, size_t len, float& layerHeight);
    static unsigned int FindFilamentUsed(const char* buf, size_t len,  float *filamentUsed, unsigned int maxFilaments);
    static void CopyParameterText(const char* src, char *dst, size_t length);

    // Buffer to hold gcode that is ready for processing
    char gcodeBuffer[gcodeBufferLength];
    unsigned int gcodeReadIndex, gcodeWriteIndex;	// head and tail indices into gcodeBuffer
    char gcodeReplyBuffer[gcodeReplyBufferLength];
    StringRef gcodeReply;
	uint16_t seq;									// reply sequence number, so that the client can tell if a json reply is new or not

    // Misc
    char password[SHORT_STRING_LENGTH + 1];
    char myName[SHORT_STRING_LENGTH + 1];

    Platform* platform;
    bool webserverActive;
    const ConnectionState *readingConnection;

    float lastTime;
    float longWait;
};


#endif
