/**************************************************************************/
/*                                                                        */
/* Copyright (c) 2001, 2011 NoMachine (http://www.nomachine.com)          */
/* Copyright (c) 2008-2014 Oleksandr Shneyder <o.shneyder@phoca-gmbh.de>  */
/* Copyright (c) 2014-2016 Ulrich Sibiller <uli42@gmx.de>                 */
/* Copyright (c) 2014-2016 Mihai Moldovan <ionic@ionic.de>                */
/* Copyright (c) 2011-2016 Mike Gabriel <mike.gabriel@das-netzwerkteam.de>*/
/* Copyright (c) 2015-2016 Qindel Group (http://www.qindel.com)           */
/*                                                                        */
/* NXCOMP, NX protocol compression and NX extensions to this software     */
/* are copyright of the aforementioned persons and companies.             */
/*                                                                        */
/* Redistribution and use of the present software is allowed according    */
/* to terms specified in the file LICENSE.nxcomp which comes in the       */
/* source distribution.                                                   */
/*                                                                        */
/* All rights reserved.                                                   */
/*                                                                        */
/* NOTE: This software has received contributions from various other      */
/* contributors, only the core maintainers and supporters are listed as   */
/* copyright holders. Please contact us, if you feel you should be listed */
/* as copyright holder, as well.                                          */
/*                                                                        */
/**************************************************************************/

#ifndef Statistics_H
#define Statistics_H

#include "NXproto.h"

#include "Misc.h"
#include "Timestamp.h"

class Proxy;

//
// Opcode 255 is for generic requests, 1 is for
// generic replies (those which haven't a speci-
// fic differential encoding), opcode 0 is for
// generic messages from the auxiliary channels.
//

#define STATISTICS_OPCODE_MAX       256

//
// Maximum length of the buffer allocated for
// the statistics output.
//

#define STATISTICS_LENGTH           16384

//
// Query type.
//

#define TOTAL_STATS                 1
#define PARTIAL_STATS               2
#define NO_STATS                    3

//
// Log level.
//

#define PANIC
#define WARNING
#undef  TEST
#undef  DEBUG

//
// Log the operations related to updating
// the control tokens.
//

#undef  TOKEN

class Statistics
{
  public:

  //
  // Use the proxy class to get access
  // to the message stores.
  //

  Statistics(Proxy *proxy);

  ~Statistics();

  void addIdleTime(unsigned int numMs)
  {
    transportPartial_.idleTime_ += numMs;
    transportTotal_.idleTime_ += numMs;
  }

  void subIdleTime(unsigned int numMs)
  {
    transportPartial_.idleTime_ -= numMs;
    transportTotal_.idleTime_ -= numMs;
  }

  void addReadTime(unsigned int numMs)
  {
    transportPartial_.readTime_ += numMs;
    transportTotal_.readTime_ += numMs;
  }

  void subReadTime(unsigned int numMs)
  {
    transportPartial_.readTime_ -= numMs;
    transportTotal_.readTime_ -= numMs;
  }

  void addWriteTime(unsigned int numMs)
  {
    transportPartial_.writeTime_ += numMs;
    transportTotal_.writeTime_ += numMs;
  }

  void subWriteTime(unsigned int numMs)
  {
    transportPartial_.writeTime_ -= numMs;
    transportTotal_.writeTime_ -= numMs;
  }

  void addBytesIn(unsigned int numBytes)
  {
    transportPartial_.proxyBytesIn_ += numBytes;
    transportTotal_.proxyBytesIn_ += numBytes;
  }

  double getBytesIn()
  {
    return transportTotal_.proxyBytesIn_;
  }

  void addBytesOut(unsigned int numBytes)
  {
    transportPartial_.proxyBytesOut_ += numBytes;
    transportTotal_.proxyBytesOut_ += numBytes;
  }

  double getBytesOut()
  {
    return transportTotal_.proxyBytesOut_;
  }

  void addFrameIn()
  {
    transportPartial_.proxyFramesIn_++;
    transportTotal_.proxyFramesIn_++;

    #ifdef TEST
    *logofs << "Statistics: Updated total proxy frames in to "
            << transportTotal_.proxyFramesIn_ << " at "
            << strMsTimestamp() << ".\n" << logofs_flush;
    #endif
  }

  void addFrameOut()
  {
    transportPartial_.proxyFramesOut_++;
    transportTotal_.proxyFramesOut_++;

    #ifdef TEST
    *logofs << "Statistics: Updated total proxy frames out to "
            << transportTotal_.proxyFramesOut_ << " at "
            << strMsTimestamp() << ".\n"
            << logofs_flush;
    #endif
  }

  void addWriteOut()
  {
    transportPartial_.proxyWritesOut_++;
    transportTotal_.proxyWritesOut_++;

    #ifdef TEST
    *logofs << "Statistics: Updated total proxy writes out to "
            << transportTotal_.proxyWritesOut_ << " at "
            << strMsTimestamp() << ".\n" << logofs_flush;
    #endif
  }

  void addCompressedBytes(unsigned int bytesIn, unsigned int bytesOut);

  void addDecompressedBytes(unsigned int bytesIn, unsigned int bytesOut)
  {
    transportPartial_.decompressedBytesIn_ += bytesIn;
    transportTotal_.decompressedBytesIn_ += bytesIn;

    transportPartial_.decompressedBytesOut_ += bytesOut;
    transportTotal_.decompressedBytesOut_ += bytesOut;
  }

  void addFramingBits(unsigned int bitsOut)
  {
    transportPartial_.framingBitsOut_ += bitsOut;
    transportTotal_.framingBitsOut_ += bitsOut;

    proxyData_.protocolCount_ += bitsOut;
  }

  void addCachedRequest(unsigned int opcode)
  {
    protocolPartial_.requestCached_[opcode]++;
    protocolTotal_.requestCached_[opcode]++;
  }

  void addRenderCachedRequest(unsigned int opcode)
  {
    protocolPartial_.renderRequestCached_[opcode]++;
    protocolTotal_.renderRequestCached_[opcode]++;
  }

  void addRepliedRequest(unsigned int opcode)
  {
    protocolPartial_.requestReplied_[opcode]++;
    protocolTotal_.requestReplied_[opcode]++;
  }

  void addCachedReply(unsigned int opcode)
  {
    protocolPartial_.replyCached_[opcode]++;
    protocolTotal_.replyCached_[opcode]++;
  }

  void addRequestBits(unsigned int opcode, unsigned int bitsIn, unsigned int bitsOut)
  {
    #ifdef DEBUG
    *logofs << "Statistcs: Added " << bitsIn << " bits in and "
            << bitsOut << " bits out to opcode " << opcode
            << ".\n" << logofs_flush;
    #endif

    protocolPartial_.requestCount_[opcode]++;
    protocolTotal_.requestCount_[opcode]++;

    protocolPartial_.requestBitsIn_[opcode] += bitsIn;
    protocolTotal_.requestBitsIn_[opcode] += bitsIn;

    protocolPartial_.requestBitsOut_[opcode] += bitsOut;
    protocolTotal_.requestBitsOut_[opcode] += bitsOut;

    //
    // Don't account the split bits
    // to the control token.
    //

    if (opcode != X_NXSplitData && opcode != X_NXSplitEvent)
    {
      proxyData_.protocolCount_ += bitsOut;
    }
  }

  void addRenderRequestBits(unsigned int opcode, unsigned int bitsIn, unsigned int bitsOut)
  {
    #ifdef DEBUG
    *logofs << "Statistcs: Added " << bitsIn << " bits in and "
            << bitsOut << " bits out to render opcode " << opcode
            << ".\n" << logofs_flush;
    #endif

    protocolPartial_.renderRequestCount_[opcode]++;
    protocolTotal_.renderRequestCount_[opcode]++;

    protocolPartial_.renderRequestBitsIn_[opcode] += bitsIn;
    protocolTotal_.renderRequestBitsIn_[opcode] += bitsIn;

    protocolPartial_.renderRequestBitsOut_[opcode] += bitsOut;
    protocolTotal_.renderRequestBitsOut_[opcode] += bitsOut;
  }

  void addReplyBits(unsigned int opcode, unsigned int bitsIn, unsigned int bitsOut)
  {
    protocolPartial_.replyCount_[opcode]++;
    protocolTotal_.replyCount_[opcode]++;

    protocolPartial_.replyBitsIn_[opcode] += bitsIn;
    protocolTotal_.replyBitsIn_[opcode] += bitsIn;

    protocolPartial_.replyBitsOut_[opcode] += bitsOut;
    protocolTotal_.replyBitsOut_[opcode] += bitsOut;

    proxyData_.protocolCount_ += bitsOut;
  }

  void addEventBits(unsigned int opcode, unsigned int bitsIn, unsigned int bitsOut)
  {
    protocolPartial_.eventCount_[opcode]++;
    protocolTotal_.eventCount_[opcode]++;

    protocolPartial_.eventBitsIn_[opcode] += bitsIn;
    protocolTotal_.eventBitsIn_[opcode] += bitsIn;

    protocolPartial_.eventBitsOut_[opcode] += bitsOut;
    protocolTotal_.eventBitsOut_[opcode] += bitsOut;

    proxyData_.protocolCount_ += bitsOut;
  }

  void addCupsBits(unsigned int bitsIn, unsigned int bitsOut)
  {
    protocolPartial_.cupsCount_++;
    protocolTotal_.cupsCount_++;

    protocolPartial_.cupsBitsIn_ += bitsIn;
    protocolTotal_.cupsBitsIn_ += bitsIn;

    protocolPartial_.cupsBitsOut_ += bitsOut;
    protocolTotal_.cupsBitsOut_ += bitsOut;
  }

  void addSmbBits(unsigned int bitsIn, unsigned int bitsOut)
  {
    protocolPartial_.smbCount_++;
    protocolTotal_.smbCount_++;

    protocolPartial_.smbBitsIn_ += bitsIn;
    protocolTotal_.smbBitsIn_ += bitsIn;

    protocolPartial_.smbBitsOut_ += bitsOut;
    protocolTotal_.smbBitsOut_ += bitsOut;
  }

  void addMediaBits(unsigned int bitsIn, unsigned int bitsOut)
  {
    protocolPartial_.mediaCount_++;
    protocolTotal_.mediaCount_++;

    protocolPartial_.mediaBitsIn_ += bitsIn;
    protocolTotal_.mediaBitsIn_ += bitsIn;

    protocolPartial_.mediaBitsOut_ += bitsOut;
    protocolTotal_.mediaBitsOut_ += bitsOut;
  }

  void addHttpBits(unsigned int bitsIn, unsigned int bitsOut)
  {
    protocolPartial_.httpCount_++;
    protocolTotal_.httpCount_++;

    protocolPartial_.httpBitsIn_ += bitsIn;
    protocolTotal_.httpBitsIn_ += bitsIn;

    protocolPartial_.httpBitsOut_ += bitsOut;
    protocolTotal_.httpBitsOut_ += bitsOut;
  }

  void addFontBits(unsigned int bitsIn, unsigned int bitsOut)
  {
    protocolPartial_.fontCount_++;
    protocolTotal_.fontCount_++;

    protocolPartial_.fontBitsIn_ += bitsIn;
    protocolTotal_.fontBitsIn_ += bitsIn;

    protocolPartial_.fontBitsOut_ += bitsOut;
    protocolTotal_.fontBitsOut_ += bitsOut;
  }

  void addSlaveBits(unsigned int bitsIn, unsigned int bitsOut)
  {
    protocolPartial_.slaveCount_++;
    protocolTotal_.slaveCount_++;

    protocolPartial_.slaveBitsIn_ += bitsIn;
    protocolTotal_.slaveBitsIn_ += bitsIn;

    protocolPartial_.slaveBitsOut_ += bitsOut;
    protocolTotal_.slaveBitsOut_ += bitsOut;
  }

  void addPackedBytesIn(unsigned int numBytes)
  {
    packedPartial_.packedBytesIn_ += numBytes;
    packedTotal_.packedBytesIn_ += numBytes;
  }

  void addPackedBytesOut(unsigned int numBytes)
  {
    packedPartial_.packedBytesOut_ += numBytes;
    packedTotal_.packedBytesOut_ += numBytes;
  }

  void addSplit()
  {
    splitPartial_.splitCount_++;
    splitTotal_.splitCount_++;
  }

  void addSplitAborted()
  {
    splitPartial_.splitAborted_++;
    splitTotal_.splitAborted_++;
  }

  void addSplitAbortedBytesOut(unsigned int numBytes)
  {
    splitPartial_.splitAbortedBytesOut_ += numBytes;
    splitTotal_.splitAbortedBytesOut_ += numBytes;
  }

  //
  // Add the recorded protocol data to the proxy
  // token counters. We want to send bpth the token
  // request message and the data payload using a
  // single system write, so we must guess how many
  // output bytes we will generate.
  //

  void updateControlToken(int &count)
  {
    //
    // Total is the total number of protocol bytes
    // generated so far. We have saved the number
    // of bytes generated the last time the function
    // was called so we can calculate the difference.
    //
    // The number of protocol bits is updated as soon
    // as new bits are accumulated, to avoid summing
    // up all the opcodes in this routine. We add a
    // byte to the control bytes difference to account
    // for the framing bits that are very likely to
    // be added to the payload.
    //

    double total = proxyData_.protocolCount_ / 8;

    double diff = total - proxyData_.controlCount_ + 1;

    #if defined(TEST) || defined(TOKEN)
    *logofs << "Statistics: TOKEN! Protocol bytes are "
            << total  << " control bytes are "
            << proxyData_.controlCount_ << " difference is "
            << diff << ".\n" << logofs_flush;
    #endif

    count += (int) (diff / proxyData_.streamRatio_);

    #if defined(TEST) || defined(TOKEN)
    *logofs << "Statistics: TOKEN! Adding "
            << (int) (diff / proxyData_.streamRatio_)
            << " bytes to the control token with ratio "
            << proxyData_.streamRatio_ << ".\n"
            << logofs_flush;
    #endif

    proxyData_.controlCount_ = total;

    #if defined(TEST) || defined(TOKEN)
    *logofs << "Statistics: TOKEN! New control token has "
            << count << " bytes with total control bytes "
            << proxyData_.controlCount_ << ".\n"
            << logofs_flush;
    #endif
  }

  void updateSplitToken(int &count)
  {
    double total = (protocolTotal_.requestBitsOut_[X_NXSplitData] +
                        protocolTotal_.eventBitsOut_[X_NXSplitEvent]) / 8;

    double diff = total - proxyData_.splitCount_;

    #if defined(TEST) || defined(TOKEN)
    *logofs << "Statistics: TOKEN! Protocol bytes are "
            << total  << " split bytes are "
            << proxyData_.splitCount_ << " difference is "
            << diff << ".\n" << logofs_flush;
    #endif

    count += (int) (diff / proxyData_.streamRatio_);

    #if defined(TEST) || defined(TOKEN)
    *logofs << "Statistics: TOKEN! Adding "
            << (int) (diff / proxyData_.streamRatio_)
            << " bytes to the split token with ratio "
            << proxyData_.streamRatio_ << ".\n"
            << logofs_flush;
    #endif

    proxyData_.splitCount_ = total;

    #if defined(TEST) || defined(TOKEN)
    *logofs << "Statistics: TOKEN! New split token has "
            << count << " bytes with total split bytes "
            << proxyData_.splitCount_ << ".\n"
            << logofs_flush;
    #endif
  }

  void updateDataToken(int &count)
  {
    double total = (protocolTotal_.cupsBitsOut_ +
                        protocolTotal_.smbBitsOut_ +
                            protocolTotal_.mediaBitsOut_ +
                                protocolTotal_.httpBitsOut_ +
                                    protocolTotal_.fontBitsOut_ +
                                        protocolTotal_.slaveBitsOut_) / 8;

    double diff = total - proxyData_.dataCount_;

    #if defined(TEST) || defined(TOKEN)
    *logofs << "Statistics: TOKEN! Protocol bytes are "
            << total  << " data bytes are "
            << proxyData_.dataCount_ << " difference is "
            << diff << ".\n" << logofs_flush;
    #endif

    count += (int) (diff / proxyData_.streamRatio_);

    #if defined(TEST) || defined(TOKEN)
    *logofs << "Statistics: TOKEN! Adding "
            << (int) (diff / proxyData_.streamRatio_)
            << " bytes to the data token with ratio "
            << proxyData_.streamRatio_ << ".\n"
            << logofs_flush;
    #endif

    proxyData_.dataCount_ = total;

    #if defined(TEST) || defined(TOKEN)
    *logofs << "Statistics: TOKEN! New data token has "
            << count << " bytes with total data bytes "
            << proxyData_.dataCount_ << ".\n"
            << logofs_flush;
    #endif
  }

  //
  // Update the current bitrate.
  //

  void updateBitrate(int bytes);

  //
  // Return the current bitrate.
  //

  int getBitrateInShortFrame()
  {
    return bitrateInShortFrame_;
  }

  int getBitrateInLongFrame()
  {
    return bitrateInLongFrame_;
  }

  int getTopBitrate()
  {
    return topBitrate_;
  }

  void resetTopBitrate()
  {
    topBitrate_ = 0;
  }

  double getStreamRatio()
  {
    return proxyData_.streamRatio_;
  }

  //
  // Manage the congestion level.
  //

  void updateCongestion(int remaining, int limit);

  double getCongestionInFrame()
  {
    return congestionInFrame_;
  }

  //
  // Produce a dump of the statistics on
  // the provided buffer.
  //

  int getClientCacheStats(int type, char *&buffer);
  int getClientProtocolStats(int type, char *&buffer);
  int getClientOverallStats(int type, char *&buffer);

  int getServerCacheStats(int type, char *&buffer);
  int getServerProtocolStats(int type, char *&buffer);
  int getServerOverallStats(int type, char *&buffer);

  int resetPartialStats();

  private:

  int getTimeStats(int type, char *&buffer);
  int getServicesStats(int type, char *&buffer);
  int getFramingStats(int type, char *&buffer);
  int getBitrateStats(int type, char *&buffer);
  int getStreamStats(int type, char *&buffer);
  int getSplitStats(int type, char *&buffer);

  struct T_protocolData
  {
    double requestCached_[STATISTICS_OPCODE_MAX];
    double requestReplied_[STATISTICS_OPCODE_MAX];
    double requestCount_[STATISTICS_OPCODE_MAX];
    double requestBitsIn_[STATISTICS_OPCODE_MAX];
    double requestBitsOut_[STATISTICS_OPCODE_MAX];

    double renderRequestCached_[STATISTICS_OPCODE_MAX];
    double renderRequestCount_[STATISTICS_OPCODE_MAX];
    double renderRequestBitsIn_[STATISTICS_OPCODE_MAX];
    double renderRequestBitsOut_[STATISTICS_OPCODE_MAX];

    double replyCached_[STATISTICS_OPCODE_MAX];
    double replyCount_[STATISTICS_OPCODE_MAX];
    double replyBitsIn_[STATISTICS_OPCODE_MAX];
    double replyBitsOut_[STATISTICS_OPCODE_MAX];

    double eventCached_[STATISTICS_OPCODE_MAX];
    double eventCount_[STATISTICS_OPCODE_MAX];
    double eventBitsIn_[STATISTICS_OPCODE_MAX];
    double eventBitsOut_[STATISTICS_OPCODE_MAX];

    double cupsCount_;
    double cupsBitsIn_;
    double cupsBitsOut_;

    double smbCount_;
    double smbBitsIn_;
    double smbBitsOut_;

    double mediaCount_;
    double mediaBitsIn_;
    double mediaBitsOut_;

    double httpCount_;
    double httpBitsIn_;
    double httpBitsOut_;

    double fontCount_;
    double fontBitsIn_;
    double fontBitsOut_;

    double slaveCount_;
    double slaveBitsIn_;
    double slaveBitsOut_;
  };

  struct T_transportData
  {
    double idleTime_;
    double readTime_;
    double writeTime_;

    double proxyBytesIn_;
    double proxyBytesOut_;

    double proxyFramesIn_;
    double proxyFramesOut_;
    double proxyWritesOut_;

    double compressedBytesIn_;
    double compressedBytesOut_;

    double decompressedBytesIn_;
    double decompressedBytesOut_;

    double framingBitsOut_;
  };

  struct T_packedData
  {
    double packedBytesIn_;
    double packedBytesOut_;
  };

  struct T_splitData
  {
    double splitCount_;
    double splitAborted_;
    double splitAbortedBytesOut_;
  };

  struct T_overallData
  {
    double overallBytesIn_;
    double overallBytesOut_;
  };

  struct T_proxyData
  {
    double protocolCount_;
    double controlCount_;
    double splitCount_;
    double dataCount_;

    double streamRatio_;
  };

  T_protocolData protocolPartial_;
  T_protocolData protocolTotal_;

  T_transportData transportPartial_;
  T_transportData transportTotal_;

  T_packedData packedPartial_;
  T_packedData packedTotal_;

  T_splitData splitPartial_;
  T_splitData splitTotal_;

  T_overallData overallPartial_;
  T_overallData overallTotal_;

  T_proxyData proxyData_;

  //
  // Used to calculate the bandwidth usage
  // of the proxy link.
  //

  T_timestamp startShortFrameTs_;
  T_timestamp startLongFrameTs_;
  T_timestamp startFrameTs_;

  int bytesInShortFrame_;
  int bytesInLongFrame_;

  int bitrateInShortFrame_;
  int bitrateInLongFrame_;

  int topBitrate_;

  double congestionInFrame_;

  //
  // Need the proxy pointer to print the
  // statistics related to the client and
  // server stores and to add the protocol
  // data to the proxy token accumulators.
  //

  Proxy *proxy_;
};

#endif /* Statistics_H */