/*
 * Copyright (C) 1996-2017 The Squid Software Foundation and contributors
 *
 * Squid software is distributed under GPLv2+ license and includes
 * contributions from numerous individuals and organizations.
 * Please see the COPYING and CONTRIBUTORS files for details.
 */

#ifndef SQUID_CLIENTSIDEREPLY_H
#define SQUID_CLIENTSIDEREPLY_H

#include "acl/forward.h"
#include "client_side_request.h"
#include "ip/forward.h"
#include "RequestFlags.h"
#include "StoreClient.h"

class ErrorState;

/* XXX make static method */

class clientReplyContext : public RefCountable, public StoreClient
{

public:
    static STCB CacheHit;
    static STCB HandleIMSReply;
    static STCB SendMoreData;

    clientReplyContext(ClientHttpRequest *);
    ~clientReplyContext();

    void saveState();
    void restoreState();
    void purgeRequest ();
    void purgeRequestFindObjectToPurge();
    void purgeDoMissPurge();
    void purgeFoundGet(StoreEntry *newEntry);
    void purgeFoundHead(StoreEntry *newEntry);
    void purgeFoundObject(StoreEntry *entry);
    void sendClientUpstreamResponse();
    void purgeDoPurgeGet(StoreEntry *entry);
    void purgeDoPurgeHead(StoreEntry *entry);
    void doGetMoreData();
    void identifyStoreObject();
    void identifyFoundObject(StoreEntry *entry);
    int storeOKTransferDone() const;
    int storeNotOKTransferDone() const;
    /// replaces current response store entry with the given one
    void setReplyToStoreEntry(StoreEntry *e, const char *reason);
    /// builds error using clientBuildError() and calls setReplyToError() below
    void setReplyToError(err_type, Http::StatusCode, const HttpRequestMethod&, char const *, Ip::Address &, HttpRequest *, const char *,
#if USE_AUTH
                         Auth::UserRequest::Pointer);
#else
                         void * unused);
#endif
    /// creates a store entry for the reply and appends err to it
    void setReplyToError(const HttpRequestMethod& method, ErrorState *err);
    /// creates a store entry for the reply and appends error reply to it
    void setReplyToReply(HttpReply *reply);
    void createStoreEntry(const HttpRequestMethod& m, RequestFlags flags);
    void removeStoreReference(store_client ** scp, StoreEntry ** ep);
    void removeClientStoreReference(store_client **scp, ClientHttpRequest *http);
    void startError(ErrorState * err);
    void processExpired();
    clientStream_status_t replyStatus();
    void processMiss();
    void traceReply(clientStreamNode * node);
    const char *storeId() const { return (http->store_id.size() > 0 ? http->store_id.termedBuf() : http->uri); }

    Http::StatusCode purgeStatus;

    /* state variable - replace with class to handle storeentries at some point */
    int lookingforstore;
    virtual void created (StoreEntry *newEntry);

    ClientHttpRequest *http;
    int headers_sz;
    store_client *sc;       /* The store_client we're using */
    StoreIOBuffer tempBuffer;   /* For use in validating requests via IMS */
    int old_reqsize;        /* ... again, for the buffer */
    size_t reqsize;
    size_t reqofs;
    char tempbuf[HTTP_REQBUF_SZ];   /* a temporary buffer if we need working storage */
#if USE_CACHE_DIGESTS

    const char *lookup_type;    /* temporary hack: storeGet() result: HIT/MISS/NONE */
#endif

    struct {

        unsigned storelogiccomplete:1;

        unsigned complete:1;        /* we have read all we can from upstream */
        bool headersSent;
    } flags;
    clientStreamNode *ourNode;  /* This will go away if/when this file gets refactored some more */

private:
    clientStreamNode *getNextNode() const;
    void makeThisHead();
    bool errorInStream(StoreIOBuffer const &result, size_t const &sizeToProcess)const ;
    void sendStreamError(StoreIOBuffer const &result);
    void pushStreamData(StoreIOBuffer const &result, char *source);
    clientStreamNode * next() const;
    StoreIOBuffer holdingBuffer;
    HttpReply *reply;
    void processReplyAccess();
    static ACLCB ProcessReplyAccessResult;
    void processReplyAccessResult(const allow_t &accessAllowed);
    void cloneReply();
    void buildReplyHeader ();
    bool alwaysAllowResponse(Http::StatusCode sline) const;
    int checkTransferDone();
    void processOnlyIfCachedMiss();
    bool processConditional(StoreIOBuffer &result);
    void cacheHit(StoreIOBuffer result);
    void handleIMSReply(StoreIOBuffer result);
    void sendMoreData(StoreIOBuffer result);
    void triggerInitialStoreRead();
    void sendClientOldEntry();
    void purgeAllCached();
    void forgetHit();
    bool blockedHit() const;

    void sendBodyTooLargeError();
    void sendPreconditionFailedError();
    void sendNotModified();
    void sendNotModifiedOrPreconditionFailedError();

    StoreEntry *old_entry;
    /* ... for entry to be validated */
    store_client *old_sc;
    time_t old_lastmod;
    String old_etag;

    bool deleting;

    typedef enum {
        crNone = 0, ///< collapsed revalidation is not allowed for this context
        crInitiator, ///< we initiated collapsed revalidation request
        crSlave ///< we collapsed on the existing revalidation request
    } CollapsedRevalidation;

    CollapsedRevalidation collapsedRevalidation;

    CBDATA_CLASS2(clientReplyContext);
};

#endif /* SQUID_CLIENTSIDEREPLY_H */

