/// \file dtsc.h
/// Holds all headers for DDVTECH Stream Container parsing/generation.

#pragma once
#include <vector>
#include <iostream>
#include <stdint.h> //for uint64_t
#include <string>
#include <deque>
#include <set>
#include <stdio.h> //for FILE
#include "json.h"
#include "socket.h"
#include "timing.h"

namespace DTSC {
  bool isFixed(JSON::Value & metadata);

  /// This enum holds all possible datatypes for DTSC packets.
  enum datatype{
    AUDIO, ///< Stream Audio data
    VIDEO, ///< Stream Video data
    META, ///< Stream Metadata
    PAUSEMARK, ///< Pause marker
    MODIFIEDHEADER, ///< Modified header data.
    INVALID ///< Anything else or no data available.
  };

  extern char Magic_Header[]; ///< The magic bytes for a DTSC header
  extern char Magic_Packet[]; ///< The magic bytes for a DTSC packet
  extern char Magic_Packet2[]; ///< The magic bytes for a DTSC packet version 2

  /// A simple structure used for ordering byte seek positions.
  struct seekPos {
    bool operator < (const seekPos& rhs) const {
      if (seekTime < rhs.seekTime){
        return true;
      }else{
        if (seekTime == rhs.seekTime){
          if (bytePos < rhs.bytePos){
            return true;
          }else{
            if (trackID < rhs.trackID){
              return true;
            }
          }
        }
      }
      return false;
    }
    long long unsigned int seekTime;
    long long unsigned int bytePos;
    unsigned int trackID;
  };

  /// A simple structure used for ordering byte seek positions.
  struct livePos {
    livePos(){
      seekTime = 0;
      trackID = 0;
    }
    livePos(const livePos & rhs){
      seekTime = rhs.seekTime;
      trackID = rhs.trackID;
    }
    void operator = (const livePos& rhs) {
      seekTime = rhs.seekTime;
      trackID = rhs.trackID;
    }
    bool operator == (const livePos& rhs) {
      return seekTime == rhs.seekTime && trackID == rhs.trackID;
    }
    bool operator != (const livePos& rhs) {
      return seekTime != rhs.seekTime || trackID != rhs.trackID;
    }
    bool operator < (const livePos& rhs) const {
      if (seekTime < rhs.seekTime){
        return true;
      }else{
        if (seekTime > rhs.seekTime){
          return false;
        }
      }
      return (trackID < rhs.trackID);
    }
    volatile long long unsigned int seekTime;
    volatile unsigned int trackID;
  };

  /// A part from the DTSC::Stream ringbuffer.
  /// Holds information about a buffer that will stay consistent
  class Ring{
    public:
      Ring(livePos v);
      livePos b;
      //volatile unsigned int b; ///< Holds current number of buffer. May and is intended to change unexpectedly!
      volatile bool waiting; ///< If true, this Ring is currently waiting for a buffer fill.
      volatile bool starved; ///< If true, this Ring can no longer receive valid data.
      volatile bool updated; ///< If true, this Ring should write a new header.
      volatile int playCount;
  };

  class Part{
    public:
      long getSize();
      void setSize(long newSize);
      short getDuration();
      void setDuration(short newDuration);
      long getOffset();
      void setOffset(long newOffset);
      char* getData();
    private:
      char data[9];
  };

  class Key{
    public:
      long long unsigned int getBpos();
      void setBpos(long long unsigned int newBpos);
      long getLength();
      void setLength(long newLength);
      unsigned short getNumber();
      void setNumber(unsigned short newNumber);
      short getParts();
      void setParts(short newParts);
      long getTime();
      void setTime(long newTime);
      char* getData();
    private:
      char data[16];
  };

  class Fragment{
    public:
      long getDuration();
      void setDuration(long newDuration);
      char getLength();
      void setLength(char newLength);
      short getNumber();
      void setNumber(short newNumber);
      long getSize();
      void setSize(long newSize);
      char* getData();
    private:
      char data[11];
  };

  class readOnlyTrack{
    public:
      readOnlyTrack();
      readOnlyTrack(JSON::Value & trackRef);
      int getSendLen();
      void send(Socket::Connection & conn);
      std::string getIdentifier();
      std::string getWritableIdentifier();
      JSON::Value toJSON();
      long long unsigned int fragLen;
      Fragment* fragments;
      long long unsigned int keyLen;
      Key* keys;
      long long unsigned int partLen;
      Part* parts;
      int trackID;
      int firstms;
      int lastms;
      int bps;
      int missedFrags;
      std::string init;
      std::string codec;
      std::string type;
      //audio only
      int rate;
      int size;
      int channels;
      //video only
      int width;
      int height;
      int fpks;
      //vorbis and theora only
      std::string idHeader;
      std::string commentHeader;
  };

  class Track : public readOnlyTrack {
    public:
      Track();
      Track(const readOnlyTrack & rhs);
      Track(JSON::Value & trackRef);
      inline operator bool() const {return parts.size();}
      void update(JSON::Value & pack);
      int getSendLen();
      void send(Socket::Connection & conn);
      JSON::Value toJSON();
      std::deque<Fragment> fragments;
      std::deque<Key> keys;
      std::deque<Part> parts;
      Key & getKey(unsigned int keyNum);
      void reset();
  };

  class readOnlyMeta {
    public:
      readOnlyMeta();
      readOnlyMeta(JSON::Value & meta);
      inline operator bool() const {return vod || live;}
      std::map<int,readOnlyTrack> tracks;
      bool vod;
      bool live;
      bool merged;
      long long int moreheader;
      long long int bufferWindow;
      void send(Socket::Connection & conn);
      JSON::Value toJSON();
      bool isFixed();
  };

  class Meta : public readOnlyMeta {
    public:
      Meta();
      Meta(const readOnlyMeta & meta);
      Meta(JSON::Value & meta);
      std::map<int,Track> tracks;
      void update(JSON::Value & pack);
      void send(Socket::Connection & conn);
      JSON::Value toJSON();
      void reset();
      bool isFixed();
  };

  /// A simple wrapper class that will open a file and allow easy reading/writing of DTSC data from/to it.
  class File{
    public:
      File();
      File(const File & rhs);
      File(std::string filename, bool create = false);
      File & operator = (const File & rhs);
      operator bool() const;
      ~File();
      readOnlyMeta & getMeta();
      long long int getLastReadPos();
      bool writeHeader(std::string & header, bool force = false);
      long long int addHeader(std::string & header);
      long int getBytePosEOF();
      long int getBytePos();
      bool reachedEOF();
      void seekNext();
      void parseNext();
      std::string & getPacket();
      JSON::Value & getJSON();
      bool seek_time(unsigned int ms);
      bool seek_time(unsigned int ms, int trackNo, bool forceSeek = false);
      bool seek_bpos(int bpos);
      void rewritePacket(std::string & newPacket, int bytePos);
      void writePacket(std::string & newPacket);
      void writePacket(JSON::Value & newPacket);
      bool atKeyframe();
      void selectTracks(std::set<int> & tracks);
    private:
      long int endPos;
      void readHeader(int pos);
      std::string strbuffer;
      JSON::Value jsonbuffer;
      JSON::Value metaStorage;
      readOnlyMeta metadata;
      std::map<int,std::string> trackMapping;
      long long int currtime;
      long long int lastreadpos;
      int currframe;
      FILE * F;
      unsigned long headerSize;
      char buffer[4];
      bool created;
      std::set<seekPos> currentPositions;
      std::set<int> selectedTracks;
  };
  //FileWriter

  /// Holds temporary data for a DTSC stream and provides functions to utilize it.
  /// Optionally also acts as a ring buffer of a certain requested size.
  /// If ring buffering mode is enabled, it will automatically grow in size to always contain at least one keyframe.
  class Stream{
    public:
      Stream();
      virtual ~Stream();
      Stream(unsigned int buffers, unsigned int bufferTime = 0);
      Meta metadata;
      JSON::Value & getPacket();
      JSON::Value & getPacket(livePos num);
      datatype lastType();
      std::string & lastData();
      bool hasVideo();
      bool hasAudio();
      bool parsePacket(std::string & buffer);
      bool parsePacket(Socket::Buffer & buffer);
      std::string & outPacket();
      std::string & outPacket(livePos num);
      std::string & outHeader();
      Ring * getRing();
      unsigned int getTime();
      void dropRing(Ring * ptr);
      int canSeekms(unsigned int ms);
      livePos msSeek(unsigned int ms, std::set<int> & allowedTracks);
      void setBufferTime(unsigned int ms);
      bool isNewest(DTSC::livePos & pos, std::set<int> & allowedTracks);
      DTSC::livePos getNext(DTSC::livePos & pos, std::set<int> & allowedTracks);
      void endStream();
      void waitForMeta(Socket::Connection & sourceSocket);
    protected:
      void cutOneBuffer();
      void resetStream();
      std::map<livePos,JSON::Value> buffers;
      std::map<int,std::set<livePos> > keyframes;
      virtual void addPacket(JSON::Value & newPack);
      virtual void addMeta(JSON::Value & newMeta);
      datatype datapointertype;
      unsigned int buffercount;
      unsigned int buffertime;
      std::map<int,std::string> trackMapping;
      virtual void deletionCallback(livePos deleting);
  };
}