/*---------------------------------------------------------------------------*/
/* appproxy.h                                                                */
/* copyright (c) innovaphone 2015                                            */
/*                                                                           */
/*---------------------------------------------------------------------------*/

#define MAX_HTTP_LINE               1000
#define CHUNKED_TRANSFER_CHUNK_SIZE 8000 /* iOS Safari stops loading if larger than 8k */
#define MAX_MANIFEST_LENGTH         10000
#define MAX_MANIFESTS_LENGTH        60000

class AppProxyService : public AppService, public IAppProxy
{
    void AppServiceApps(istd::list<AppServiceApp> * appList) override;

public:
    AppProxyService(class IIoMux * const iomux, class ISocketProvider * localSocketProvider, IWebserverPluginProvider * const webserverPluginProvider,
        ISocketProvider * tcpSocketProvider, ISocketProvider * tlsSocketProvider, AppServiceArgs * args, UAppProxy * user = NULL, bool tlsTranslate = false);
	~AppProxyService();

    void Cacheable(const char* url, const char* filename) override;
    void ConnectionMonitor(bool on) override;

	class AppInstance * CreateInstance(AppInstanceArgs * args) override;
    void SetUser(UAppProxy * user);

	class IIoMux * iomux;
    class ISocketProvider * localSocketProvider;
    class IWebserverPluginProvider * webserverPluginProvider;
    class ISocketProvider * tcpSocketProvider;
    class ISocketProvider * tlsSocketProvider;
    class UAppProxy * user;
    bool tlsTranslate;
    class AppProxy * appproxy;
};

class AppProxyManifestClose {};

class AppProxy : public UWebserverPlugin, public AppInstance, public UTask, public STaskContext,
    public UIoExecSubclass<AppProxyManifestClose>
{
    void SetExec(IIoMux * iomux, UIoExecSubclass<AppProxyManifestClose> * ioContext, AppProxyManifestClose * execContext) { iomux->SetExec(ioContext, execContext); };
    void IoExec(AppProxyManifestClose * execContext);

    void WebserverPluginRegisterPathResult(IWebserverPlugin * plugin, const char * pathName, wsp_path_type_t pathType);
    void WebserverPluginPassthroughListenResult(IWebserverPlugin * plugin, char * resourceName, const char * registeredPathForRequest, ulong64 dataSize);
    void WebserverPluginClose(IWebserverPlugin * plugin, wsp_close_reason_t reason, bool lastUser);

    void DeleteManifest(class AppProxyManifest * m);
    void Stop() override;
    void AppInstanceShutdownTimeout() override;
    void ServerCertificateUpdate(const byte * cert, size_t certLen) override;

    void TaskComplete(class ITask * const task);
    void TaskFailed(class ITask * const task);
    void TaskProgress(class ITask * const task, dword progress);

    void TryStop();

    class IWebserverPlugin * webserverPlugin;
    bool stopping;

    class TaskFileWrite * write;
    class TaskFileRead * read;
    class TaskReadManifests * readManifests;

    istd::list<class AppProxyPassthrough> passthroughList;

public:
    AppProxy(IIoMux * const iomux, AppProxyService * service, AppInstanceArgs * args);
    ~AppProxy();

    void Cacheable(const char * url, const char * filename);
    void ConnectionMonitor(bool on);
    class AppProxyCacheable * addCacheable(const char * path);
    class AppProxyCacheable * findCacheable(const char * path);
    class AppProxyManifest * findManifest(const char * fileName);
    class AppProxyManifestFile * findManifestFile(class AppProxyManifest * m, const char * fileName);
    class AppProxyManifest * findRedirect(const char * fileName);
    class AppProxyManifestFile * findAddCacheableFile(AppProxyCacheable * c, const char * fileName, const char * fileParams, const char *addType);
    void ManifestCloseComplete(class AppProxyManifest * m);
    void updateManifestFile();
    bool ConnectivityUp();

    void SocketRetainerRemoved(class AppProxySocketRetainer * retainer);
    void AppProxyPassthroughClosed(class AppProxyPassthrough * passthrough);

    class AppProxyService * service;
    char * workingPath;
    char * webserverPath;
    char * manifestsFile;
    class btree * manifests;
    class btree * redirects;
    class btree * cacheables;
    istd::list<class AppProxyManifest> manifestList;
    istd::list<class AppProxyManifest> closedManifests;
    bool manifestsUpdated;
    bool connectionMonitorOn;

    istd::list<class AppProxySocketRetainer> socketRetainers;
};

class AppProxySocketRetainerExpiryTimer : public ITimer {
public:
    AppProxySocketRetainerExpiryTimer(IIoMux * ioMux, UTimerSubclass<AppProxySocketRetainerExpiryTimer> * owner) : ITimer(ioMux, owner) {};
};

class AppProxySocketRetainer : public USocket, public istd::listElement<AppProxySocketRetainer>,
    public UTimerSubclass<AppProxySocketRetainerExpiryTimer>
{
    friend class AppProxyPassthrough;

    bool tls;
    char * host;
    char * addr;
    class ISocket * socket;
    AppProxySocketRetainerExpiryTimer expiryTimer;
    class AppProxy * appProxy;

    void SocketConnectComplete(ISocket * const socket) {};
    void SocketBindResult(ISocket * const socket, const char * localAddr, word localPort) {};
    void SocketListenResult(ISocket * const socket, const char * remoteAddr, word remotePort) {};
    void SocketAcceptComplete(ISocket * const socket) {};
    void SocketShutdown(ISocket * const socket, shutdownreason_t reason);
    void SocketSendResult(ISocket * const socket) {};
    void SocketRecvResult(ISocket * const socket, void * buf, size_t len) {};
    void SocketRecvCanceled(ISocket * const socket, void * buf);

    void TimerOnTimeout(AppProxySocketRetainerExpiryTimer * timer);

public:
    AppProxySocketRetainer(class IIoMux *iomux, class AppProxy * appProxy, bool tls, const char * host, const char * addr, ISocket * socket);
    ~AppProxySocketRetainer();

    void Shutdown();
};

class AppProxyPassthroughConnectTimer : public ITimer {
public:
    AppProxyPassthroughConnectTimer(IIoMux * ioMux, UTimerSubclass<AppProxyPassthroughConnectTimer> * owner) : ITimer(ioMux, owner) {};
};

class AppProxyPassthroughRetryTimer : public ITimer {
public:
    AppProxyPassthroughRetryTimer(IIoMux * ioMux, UTimerSubclass<AppProxyPassthroughRetryTimer> * owner) : ITimer(ioMux, owner) {};
};

class fragment;
class AppProxyPassthrough : public UWebserverPassthrough, public USocket, public UTask, public UDns, public istd::listElement<AppProxyPassthrough>,
    public UTimerSubclass<AppProxyPassthroughConnectTimer>,
    public UTimerSubclass<AppProxyPassthroughRetryTimer>
{
    void WebserverPassthroughRequestAcceptComplete(IWebserverPassthrough * const webserverPassthrough);
    void WebserverPassthroughSendResult(IWebserverPassthrough * const webserverPassthrough);
    void WebserverPassthroughRecvResult(IWebserverPassthrough * const webserverPassthrough, void * buffer, size_t len);
    void * WebserverPassthroughRecvBuffer(size_t len);
    void WebserverPassthroughRecvCanceled(IWebserverPassthrough * const webserverPassthrough, void * buffer);
    void WebserverPassthroughCloseComplete(IWebserverPassthrough * const webserverPassthrough);

    void SocketConnectComplete(ISocket * const socket);
    void SocketBindResult(ISocket * const socket, const char * localAddr, word localPort) {};
    void SocketListenResult(ISocket * const socket, const char * remoteAddr, word remotePort) {};
    void SocketAcceptComplete(ISocket * const socket) {};
    void SocketShutdown(ISocket * const socket, shutdownreason_t reason);
    void SocketSendResult(ISocket * const socket);
    void SocketRecvResult(ISocket * const socket, void * buf, size_t len);
    void SocketRecvCanceled(ISocket * const socket, void * buf);
    void SocketUpgradeToTLSComplete(ISocket* const socket) override;

    void TaskComplete(class ITask * const task);
    void TaskFailed(class ITask * const task);
    void TaskProgress(class ITask * const task, dword progress);

    void DnsGetHostByNameResult(const char * addr, bool isIPv6);

    void Forward();
    void ConnectProxy();
    void xmitRequest(char * s, unsigned l, unsigned start, unsigned end);
    void xmitHeader(char * s, unsigned l, unsigned start, unsigned end);
    void xmitEoh();
    void xmitContent(char * s, unsigned l);
    void headerSend(unsigned len, bool send);

    void tunnelHeader(char * s, unsigned l);
    void tunnelEoh();
    void recvHeader(char * s, unsigned l);
    void recvEoh();
    void recvContent(char * s, unsigned l);
    void recvPayload(char * s, unsigned l);

    void TimerOnTimeout(AppProxyPassthroughConnectTimer * timer);
    void TimerOnTimeout(AppProxyPassthroughRetryTimer * timer);

    bool tryDelete();

    class AppProxy * appProxy;
    class ISocketProvider * tcpSocketProvider;
    class ISocketProvider * tlsSocketProvider;
    bool tlsTranslate;
    class IWebserverPassthrough * passthrough;
    class ISocket * socket;
    class IDns * dns;
    bool dnsPending;
    char * hostIn;
    char * host;
    char * hostName;
    char * hostPort;
    char * hostAddr;
    char * hostPath;
    class AppProxyManifest * loadingManifest;
    class AppProxyCacheable * loadingCacheable;
    class AppProxyManifestFile * loadingFile;
    class TaskFileWrite * write;
    bool writing;
    istd::list<fragment> queue;
    class AppProxyManifest * readingManifest;
    class AppProxyManifestFile * readingFile;
    class TaskFileRead * read;
    unsigned retryInterval;
    AppProxyPassthroughConnectTimer connectTimer;
    AppProxyPassthroughRetryTimer retryTimer;
    bool closing;
    bool read_started;
    bool reading;
    bool tls;
    bool ip6;
    bool shutdown;

    bool connect_tunnel;
    class HTTPProxies * proxies;
    class HTTPProxy * proxy;
    char * proxyAddr;
    bool retryAuthenticated;

    struct {
        istd::list<fragment> header;
        unsigned fragmentOffset;
        bool content;
        unsigned contentLength;
        bool chunked;
        char * request;
        unsigned requestLen;
        unsigned ptr;
        unsigned startHdr;
        unsigned startHost;
        unsigned endHost;
        char * fileName;
        const char * fileNameShort;
        char * fileParams;
        char * fileParamsShort;
        bool eod;
        bool endChunk;
        bool cr;
        char * line;
        unsigned lineLen;
        unsigned sending;
        bool fileTypeText;
        bool httpGetMethod;
    } xmit;

    struct {
        bool content;
        unsigned contentLength;
        unsigned responseCode;
        bool responseProcessed;
        bool chunked;
        bool connectionClose;
        bool eod;
        bool endChunk;
        bool cr;
        char * encoding;
        char * type;
        char * location;
        char * cacheControl;
        char * line;
        unsigned lineLen;
        char * payload;
        unsigned payloadLen;
        unsigned payloadEol;
        bool payloadManifest;
        bool hasSent;
        unsigned sending;
    } recv;

    struct {
        unsigned responseCode;
        bool cr;
        char * line;
        unsigned lineLen;
    } tunnel;
public:
    AppProxyPassthrough(class AppProxy * appProxy, ISocketProvider * tcpSocketProvider, ISocketProvider * tlsSocketProvider, bool tlsTranslate);
    ~AppProxyPassthrough();

    void Close();
    void ShutdownTimeout();
    void LoadCacheable(bool reuse);
    void UpdateConnectionMonitor();

    bool websocket;
};

class fragment : public istd::listElement<class fragment> {
public:
    fragment(byte * buffer, unsigned len) {
        this->buffer = buffer;
        this->len = len;
    }
    ~fragment() {
        if (buffer) free(buffer);
    }

    byte * buffer;
    unsigned len;
};

class AppProxyRedirect : public btree {
    int btree_compare(void * key) { return strcmp((char *)key, redirectPath); };
    int btree_compare(class btree * b) { return strcmp(((class AppProxyRedirect *)b)->redirectPath, redirectPath); };
public:
    AppProxyRedirect(class AppProxyManifest * manifest);
    ~AppProxyRedirect();

    class AppProxyManifest * manifest;
    char * redirectPath;
    unsigned redirectLen;
};

class AppProxyManifestStartTimer : public ITimer {
public:
    AppProxyManifestStartTimer(IIoMux * ioMux, UTimerSubclass<AppProxyManifestStartTimer> * owner) : ITimer(ioMux, owner) {};
};

class AppProxyManifestFile;
class AppProxyManifest : public btree, public UHTTPClient, public UTask, public istd::listElement<class AppProxyManifest>,
    public UTimerSubclass<AppProxyManifestStartTimer>,
    public AppProxyManifestClose
{
    int btree_compare(void * key) { return strcmp((char *)key, path); };
    int btree_compare(class btree * b) { return strcmp(((class AppProxyManifest *)b)->path, path); };

    void TimerOnTimeout(AppProxyManifestStartTimer * timer);

    void HTTPClientConnectComplete(IHTTPClient * const httpClient);
    void HTTPClientShutdown(IHTTPClient * const httpClient, http_shutdown_reason_t reason);
    void HTTPClientSendResult(IHTTPClient * const httpClient);
    void HTTPClientRecvResult(IHTTPClient * const httpClient, byte * buffer, size_t len, bool transferComplete);
    void HTTPClientRecvCanceled(IHTTPClient * const httpClient, byte * buffer);
    void HTTPClientRequestComplete(IHTTPClient * const httpClient);

    void TaskComplete(class ITask * const task);
    void TaskFailed(class ITask * const task);
    void TaskProgress(class ITask * const task, dword progress);

    class AppProxy * appProxy;
    class btree * filesTree;
    istd::list<AppProxyManifestFile> filesList;
    class AppProxyManifestStartTimer start;
    char * host;
    char * hostPath;
    IHTTPClient * http;
    class TaskFileWrite * write;
    class AppProxyManifestFile * loadingFile;
    bool writing;
    bool complete;
    bool tls;
    bool closing;
    bool closed;

public:
    AppProxyManifest(class AppProxy * appProxy, const char * fileName);
    ~AppProxyManifest();
    void Close();
    void RemoveFiles();
    void ShutdownTimeout();
    void addFiles(const char * fileName, const char * encoding, const char * type, const char * data, unsigned len, bool loaded, bool tls = false, const char * host = 0, const char * hostPath = 0);
    class AppProxyManifestFile * addFile(const char * name, const char * encoding, const char * type);
    class AppProxyManifestFile * findFile(const char * name);
    void fileLoad();
    void fileLoadStarted(class AppProxyManifestFile * file);
    void fileLoadComplete(class AppProxyManifestFile * file);
    void fileLoadFailed(class AppProxyManifestFile * file);
    bool loadComplete();
    void fileReadStarted(class AppProxyManifestFile * file);
    void fileReadComplete(class AppProxyManifestFile * file);

    AppProxyRedirect redirect;

    bool manifest;
    bool removeFiles;
    istd::list<AppProxyManifestFile> fileNames;
    char * path;
    unsigned pathLen;
};

class AppProxyCacheableReuseTimer : public ITimer {
public:
    AppProxyCacheableReuseTimer(IIoMux * ioMux, UTimerSubclass<AppProxyCacheableReuseTimer> * owner) : ITimer(ioMux, owner) {};
};

class AppProxyCacheable : public btree,
    public UTimerSubclass<AppProxyCacheableReuseTimer>
{
    int btree_compare(void * key) { return strcmp((char *)key, path); };
    int btree_compare(class btree * b) { return strcmp(((class AppProxyCacheable *)b)->path, path); };
    void TimerOnTimeout(AppProxyCacheableReuseTimer * timer);

    AppProxyCacheableReuseTimer reuseTimer;

public:
    AppProxyCacheable(class IIoMux *iomux, const char * path);
    ~AppProxyCacheable();
    class AppProxyManifestFile * addFile(const char * name, const char * encoding, const char * type);
    class AppProxyManifestFile * findFile(const char * name);
    void load(AppProxyPassthrough * passthrough, bool immediate, bool reuse);
    void loadComplete(AppProxyPassthrough * passthrough);

    char * path;
    class btree * filesTree;
    AppProxyPassthrough * loadingPassthrough;
    unsigned loadingCount;
};

class AppProxyManifestFile : public btree, public istd::listElement<AppProxyManifestFile> {
    int btree_compare(void * key) { return strcmp((char *)key, name); };
    int btree_compare(class btree * b) { return strcmp(((class AppProxyManifestFile *)b)->name, name); };

public:
    AppProxyManifestFile(const char * name, const char * encoding, const char * contentType);
    ~AppProxyManifestFile();

    char * name;
    char * encoding;
    char * contentType;
    const char * type;
    time_t lastWriteTime;
    bool loading;
    bool loaded;
    unsigned readingCount;
};

class TaskReadManifests : public ITask, public UTask {
    void Start(class UTask * user);
    void Next();
    void TaskComplete(class ITask * const task);
    void TaskFailed(class ITask * const task);
    void TaskProgress(class ITask * const task, dword progress);

    class AppProxy * appProxy;
    class UTask * user;
    std::queue<char *> manifests;
    class TaskFileRead * read;

public:
    TaskReadManifests(class AppProxy * appProxy);
    ~TaskReadManifests();
    void AddManifest(const char * fileName);
};

