#pragma once
#include <string>
#include <iostream>
#include <cstdio>
#include <stdint.h>
#include <sstream>
#include <deque>
#include <algorithm>
#include "json.h"

/// Contains all MP4 format related code.
namespace MP4 {

  class Box{
    public:
      Box(char * datapointer = 0, bool manage = true);
      ~Box();
      std::string getType();
      bool isType(const char* boxType);
      bool read(std::string & newData);
      long long int boxedSize();
      long long int payloadSize();
      char * asBox();
      char * payload();
      void clear();
      std::string toPrettyString(int indent = 0);
    protected:
      //integer functions
      void setInt8(char newData, size_t index);
      char getInt8(size_t index);
      void setInt16(short newData, size_t index);
      short getInt16(size_t index);
      void setInt24(long newData, size_t index);
      long getInt24(size_t index);
      void setInt32(long newData, size_t index);
      long getInt32(size_t index);
      void setInt64(long long int newData, size_t index);
      long long int getInt64(size_t index);
      //string functions
      void setString(std::string newData, size_t index);
      void setString(char* newData, size_t size, size_t index);
      char * getString(size_t index);
      size_t getStringLen(size_t index);
      //box functions
      Box & getBox(size_t index);
      size_t getBoxLen(size_t index);
      void setBox(Box & newEntry, size_t index);
      //data functions
      bool reserve(size_t position, size_t current, size_t wanted);
      //internal variables
      char * data; ///< Holds the data of this box
      int data_size; ///< Currently reserved size
      bool managed; ///< If false, will not attempt to resize/free the data pointer.
      int payloadOffset; ///<The offset of the payload with regards to the data
  };
  //Box Class

  struct afrt_runtable{
      long firstFragment;
      long long int firstTimestamp;
      long duration;
      long discontinuity;
  };
  //fragmentRun

  /// AFRT Box class
  class AFRT: public Box{
    public:
      AFRT();
      void setVersion(char newVersion);
      long getVersion();
      void setUpdate(long newUpdate);
      long getUpdate();
      void setTimeScale(long newScale);
      long getTimeScale();
      long getQualityEntryCount();
      void setQualityEntry(std::string & newQuality, long no);
      const char * getQualityEntry(long no);
      long getFragmentRunCount();
      void setFragmentRun(afrt_runtable newRun, long no);
      afrt_runtable getFragmentRun(long no);
      std::string toPrettyString(int indent = 0);
  };
  //AFRT Box

  struct asrt_runtable{
      long firstSegment;
      long fragmentsPerSegment;
  };

  /// ASRT Box class
  class ASRT: public Box{
    public:
      ASRT();
      void setVersion(char newVersion);
      long getVersion();
      void setUpdate(long newUpdate);
      long getUpdate();
      long getQualityEntryCount();
      void setQualityEntry(std::string & newQuality, long no);
      const char* getQualityEntry(long no);
      long getSegmentRunEntryCount();
      void setSegmentRun(long firstSegment, long fragmentsPerSegment, long no);
      asrt_runtable getSegmentRun(long no);
      std::string toPrettyString(int indent = 0);
  };
  //ASRT Box

  /// ABST Box class
  class ABST: public Box{
    public:
      ABST();
      void setVersion(char newVersion);
      char getVersion();
      void setFlags(long newFlags);
      long getFlags();
      void setBootstrapinfoVersion(long newVersion);
      long getBootstrapinfoVersion();
      void setProfile(char newProfile);
      char getProfile();
      void setLive(bool newLive);
      bool getLive();
      void setUpdate(bool newUpdate);
      bool getUpdate();
      void setTimeScale(long newTimeScale);
      long getTimeScale();
      void setCurrentMediaTime(long long int newTime);
      long long int getCurrentMediaTime();
      void setSmpteTimeCodeOffset(long long int newTime);
      long long int getSmpteTimeCodeOffset();
      void setMovieIdentifier(std::string & newIdentifier);
      char * getMovieIdentifier();
      long getServerEntryCount();
      void setServerEntry(std::string & entry, long no);
      const char * getServerEntry(long no);
      long getQualityEntryCount();
      void setQualityEntry(std::string & entry, long no);
      const char * getQualityEntry(long no);
      void setDrmData(std::string newDrm);
      char * getDrmData();
      void setMetaData(std::string newMetaData);
      char * getMetaData();
      long getSegmentRunTableCount();
      void setSegmentRunTable(ASRT & table, long no);
      ASRT & getSegmentRunTable(long no);
      long getFragmentRunTableCount();
      void setFragmentRunTable(AFRT & table, long no);
      AFRT & getFragmentRunTable(long no);
      std::string toPrettyString(long indent = 0);
  };
  //ABST Box

  class MFHD: public Box{
    public:
      MFHD();
      void setSequenceNumber(long newSequenceNumber);
      long getSequenceNumber();
      std::string toPrettyString(int indent = 0);
  };
  //MFHD Box

  class MOOF: public Box{
    public:
      MOOF();
      long getContentCount();
      void setContent(Box & newContent, long no);
      Box & getContent(long no);
      std::string toPrettyString(int indent = 0);
  };
  //MOOF Box

  class TRAF: public Box{
    public:
      TRAF();
      long getContentCount();
      void setContent(Box & newContent, long no);
      Box & getContent(long no);
      std::string toPrettyString(int indent = 0);
  };
  //TRAF Box

  struct trunSampleInformation{
      long sampleDuration;
      long sampleSize;
      long sampleFlags;
      long sampleOffset;
  };
  enum trunflags{
    trundataOffset = 0x00000001,
    trunfirstSampleFlags = 0x00000004,
    trunsampleDuration = 0x00000100,
    trunsampleSize = 0x00000200,
    trunsampleFlags = 0x00000400,
    trunsampleOffsets = 0x00000800
  };
  enum sampleflags{
    noIPicture = 0x01000000,
    isIPicture = 0x02000000,
    noDisposable = 0x00400000,
    isDisposable = 0x00800000,
    isRedundant = 0x00100000,
    noRedundant = 0x00200000,
    noKeySample = 0x00010000,
    isKeySample = 0x00000000,
    MUST_BE_PRESENT = 0x1
  };
  std::string prettySampleFlags(long flag);
  class TRUN: public Box{
    public:
      TRUN();
      void setFlags(long newFlags);
      long getFlags();
      void setDataOffset(long newOffset);
      long getDataOffset();
      void setFirstSampleFlags(long newSampleFlags);
      long getFirstSampleFlags();
      long getSampleInformationCount();
      void setSampleInformation(trunSampleInformation newSample, long no);
      trunSampleInformation getSampleInformation(long no);
      std::string toPrettyString(long indent = 0);
  };

  enum tfhdflags{
    tfhdBaseOffset = 0x000001,
    tfhdSampleDesc = 0x000002,
    tfhdSampleDura = 0x000008,
    tfhdSampleSize = 0x000010,
    tfhdSampleFlag = 0x000020,
    tfhdNoDuration = 0x010000,
  };
  class TFHD: public Box{
    public:
      TFHD();
      void setFlags(long newFlags);
      long getFlags();
      void setTrackID(long newID);
      long getTrackID();
      void setBaseDataOffset(long long newOffset);
      long long getBaseDataOffset();
      void setSampleDescriptionIndex(long newIndex);
      long getSampleDescriptionIndex();
      void setDefaultSampleDuration(long newDuration);
      long getDefaultSampleDuration();
      void setDefaultSampleSize(long newSize);
      long getDefaultSampleSize();
      void setDefaultSampleFlags(long newFlags);
      long getDefaultSampleFlags();
      std::string toPrettyString(long indent = 0);
  };

  struct afraentry{
      long long time;
      long long offset;
  };
  struct globalafraentry{
      long long time;
      long segment;
      long fragment;
      long long afraoffset;
      long long offsetfromafra;
  };
  class AFRA: public Box{
    public:
      AFRA();
      void setVersion(long newVersion);
      long getVersion();
      void setFlags(long newFlags);
      long getFlags();
      void setLongIDs(bool newVal);
      bool getLongIDs();
      void setLongOffsets(bool newVal);
      bool getLongOffsets();
      void setGlobalEntries(bool newVal);
      bool getGlobalEntries();
      void setTimeScale(long newVal);
      long getTimeScale();
      long getEntryCount();
      void setEntry(afraentry newEntry, long no);
      afraentry getEntry(long no);
      long getGlobalEntryCount();
      void setGlobalEntry(globalafraentry newEntry, long no);
      globalafraentry getGlobalEntry(long no);
      std::string toPrettyString(long indent = 0);
  };

  class AVCC: public Box{
    public:
      AVCC();
      void setVersion(long newVersion);
      long getVersion();
      void setProfile(long newProfile);
      long getProfile();
      void setCompatibleProfiles(long newCompatibleProfiles);
      long getCompatibleProfiles();
      void setLevel(long newLevel);
      long getLevel();
      void setSPSNumber(long newSPSNumber);
      long getSPSNumber();
      void setSPS(std::string newSPS);
      long getSPSLen();
      char* getSPS();
      void setPPSNumber(long newPPSNumber);
      long getPPSNumber();
      void setPPS(std::string newPPS);
      long getPPSLen();
      char* getPPS();
      std::string asAnnexB();
      void setPayload(std::string newPayload);
      std::string toPrettyString(long indent = 0);
  };

  class SDTP: public Box{
    public:
      SDTP();
      void setVersion(long newVersion);
      long getVersion();
      void setValue(long newValue, size_t index);
      long getValue(size_t index);
  };
}