/*
** nsvlib.cpp - NSV file/bitstream reading/writing code
** 
** Copyright (C) 2001-2002 Nullsoft, Inc.
**
** Confidential Subject to NDA
*/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <bfc/platform/strcmp.h>

#include "nsvlib.h"

#define NSV_HDR_DWORD (NSV_MAKETYPE('N','S','V','f'))

#define NSV_SYNC_HEADERLEN_BITS 192
#define NSV_SYNC_DWORD (NSV_MAKETYPE('N','S','V','s'))
#define NSV_NONSYNC_HEADERLEN_BITS 56
#define NSV_NONSYNC_WORD 0xBEEF

#define NSV_INVALID_SYNC_OFFSET 0x80000000

long	glSyncFrameCount = 0l;
long	glCounterNSVf = 0l;
long	glNonSyncFrameCount = 0l;

/*
  NSV sync packet header
  32 bits: NSV_SYNC_DWORD
  32 bits: video format
  32 bits: audio format
  16 bits: width
  16 bits: height
  8  bits: framerate (see getfrate/setfrate)


  16 bits: audio/video sync offset

or

  NSV nonsync packet header
  16 bits: NSV_NONSYNC_WORD

then 

4  bits: # aux data channels present (max 15)
20 bits: video data + aux channels length
16 bits: audio data length

--------------------------------
sync:
  192 bit header, 
  136 bits are invariant
nonsync:
  56 bit header
  16 bits are invariant

*/




static int is_type_char_valid(int c)
{
  c&=0xff;
  return (c >= 'a' && c <= 'z') ||
         (c >= 'A' && c <= 'Z') ||
         (c >= '0' && c <= '9') ||
         c == ' ' || c == '-' || 
         c == '.' || c == '_';
}

static int is_type_valid(unsigned int t)
{
  return (t&0xff) != ' ' &&
          is_type_char_valid(t>>24) &&
          is_type_char_valid(t>>16) &&
          is_type_char_valid(t>>8) &&
          is_type_char_valid(t);
}


void nsv_type_to_string(unsigned int t, char *out)
{
  if (is_type_valid(t))
  {
    out[0]=(t)&0xff;
    out[1]=(t>>8)&0xff;
    out[2]=(t>>16)&0xff;
    out[3]=(t>>24)&0xff;
    out[4]=0;
    int x=3;
    while (out[x]==' ' && x > 0) out[x--]=0;
  }
  else *out=0;
}

unsigned int nsv_string_to_type(char *in)
{
  int n;
  unsigned int ret=*in;
  if (*in == ' ' || !is_type_char_valid(*in)) return 0;
  in++;
  for (n = 0; n < 3; n ++)
  {
    if (!is_type_char_valid(*in)) break;
    ret|=(*in<<(8+8*n));
    in++;
  }
  if (*in) return 0;
  return ret;
}

// frate is specified
// XYYYYYZZ
// if !X, framerate is YYYYYZZ (1-127)
// otherwise:
//   ZZ indexes base
//   YYYYY is scale (0-32). 
//     if YYYYY < 16, then scale = 1/(YYYY+1)
//     otherwise scale = YYYYY-15


static double frate2double(unsigned char fr)
{
  static double fratetab[]=
  {
    30.0,
    30.0*1000.0/1001.0,
    25.0,
    24.0*1000.0/1001.0,
  };
  if (!(fr&0x80)) return (double)fr;

  double sc;
  int d=(fr&0x7f)>>2;
  if (d < 16) sc=1.0/(double)(d+1);
  else sc=d-15;

  return fratetab[fr&3]*sc; 
}



static unsigned char double2frate(double fr)
{
  int best=0;
  double best_v=1000000.0;
  int x;
  for (x = 0; x < 256; x ++)
  {
    double this_v=(fr-frate2double(x));

    if (this_v<0) this_v=-this_v;
    if (this_v < best_v) 
    {
      best_v=this_v;
      best=x;
    }
  }
  return (unsigned char) best;
}



nsv_Packeter::nsv_Packeter()
{
  vidfmt=audfmt=0;
  width=height=0;
  framerate_idx=0;
  framerate=0.0;
  syncoffset_cur=0;
  video=NULL;
  audio=NULL;
  video_len=0;
  audio_len=0;
  aux_used=0;
}

void nsv_Packeter::setVidFmt(unsigned int vfmt, unsigned int w, unsigned int h, double frt)
{
  vidfmt=vfmt;
  width=w;
  height=h;
  framerate=frt;
  framerate_idx=double2frate(frt);
}

nsv_Packeter::~nsv_Packeter()
{
}

int nsv_Packeter::packet(nsv_OutBS &bs)
{
  int total_auxlen=0;
  int x;
  if (width >= (1<<16) || height >= (1<<16) || 
      !framerate_idx || framerate_idx > 255 ||
      !is_type_valid(audfmt) || 
      !is_type_valid(vidfmt) ||
      video_len > NSV_MAX_VIDEO_LEN || 
      audio_len > NSV_MAX_AUDIO_LEN ||
      aux_used > NSV_MAX_AUXSTREAMS || 
      aux_used < 0    
    ) return -1;

  for (x = 0; x < aux_used; x ++)
  {
    if (aux_len[x] > NSV_MAX_AUX_LEN) return -1;
    total_auxlen+=aux_len[x]+6;
  }
  
  if (is_sync_frame)
  {
    bs.putbits(32,NSV_SYNC_DWORD);
    bs.putbits(32,vidfmt);
    bs.putbits(32,audfmt);
    bs.putbits(16,width);
    bs.putbits(16,height);
    bs.putbits(8 ,framerate_idx);
    bs.putbits(16,syncoffset_cur);
  }
  else
  {
    bs.putbits(16,NSV_NONSYNC_WORD);
  }
  
  bs.putbits(4,aux_used); // no aux data channels for our streams yet
  bs.putbits(20,video_len+total_auxlen);
  bs.putbits(16,audio_len);

  for (x = 0; x < aux_used; x ++)
  {
    bs.putbits(16,aux_len[x]); // length of 0 for aux channels
    bs.putbits(32,aux_types[x]);
    if (aux_len[x]) bs.putdata(aux_len[x]*8,aux[x]);
  }

  if (video_len) bs.putdata(video_len*8,video);
  if (audio_len) bs.putdata(audio_len*8,audio);

  return 0;
}


void nsv_Unpacketer::reset(int full)
{
  synched=0;
  is_sync_frame=0;
  syncoffset_cur=0;
  syncoffset=NSV_INVALID_SYNC_OFFSET;

  if (full)
  {
    m_auxbs=NULL;
    m_audiobs=NULL;
    m_videobs=NULL;
    m_eof=0;
    vidfmt=0;
    audfmt=0;
    valid=0;
    width=0;
    height=0;
    framerate=0.0;
    framerate_idx=0;
  }
}


// returns 0 on success, >0 on needs (at least X bytes) more data, 
//   -1 on error (no header found in block)
int nsv_Unpacketer::unpacket(nsv_InBS &bs)
{
  int gotframe=0;
  unsigned int num_aux=0;
  unsigned int vl=0;
  unsigned int al=0;
   
  while (bs.avail()>=NSV_NONSYNC_HEADERLEN_BITS)
  {
    if (valid && synched)
    {
      if (bs.avail() < NSV_NONSYNC_HEADERLEN_BITS)
        return m_eof?-1:(NSV_NONSYNC_HEADERLEN_BITS- (int)(bs.avail())/8);

      unsigned int d=bs.getbits(16);
      if (d == NSV_NONSYNC_WORD)
      {
		  glNonSyncFrameCount++;
        num_aux=bs.getbits(4);
        vl=bs.getbits(20);
        al=bs.getbits(16);
        if (al >= NSV_MAX_AUDIO_LEN || 
            vl >= (NSV_MAX_VIDEO_LEN+num_aux*(NSV_MAX_AUX_LEN+6)))
        {
          bs.seek(-NSV_NONSYNC_HEADERLEN_BITS);
        }
        else
        {
          if ((unsigned int)bs.avail() < 8*(vl+al)+(m_eof?0:32))
          {
            int l=(al+vl+32/8)- (int)(bs.avail()/8);
            bs.seek(-NSV_NONSYNC_HEADERLEN_BITS);
            return m_eof?-1:l;
          }

          if ((unsigned int)bs.avail() >= 8*(vl+al)+32)
          {
            bs.seek(8*(vl+al));
            unsigned int a32=bs.getbits(32);
            bs.seek(-32);
            unsigned int a16=bs.getbits(16);
            bs.seek(-16);
            bs.seek(-8*(vl+al));
            if (a16 != NSV_NONSYNC_WORD && a32 != NSV_SYNC_DWORD)
            {
              bs.seek(-NSV_NONSYNC_HEADERLEN_BITS);
            }
            else gotframe=NSV_NONSYNC_HEADERLEN_BITS;
          }
          else gotframe=NSV_NONSYNC_HEADERLEN_BITS;
        }
      }
      else bs.seek(-16);
    } // inf.valid && inf.synched

    // gotframe is set if we successfully got a nonsync frame, otherwise
    // let's see if we can't interpret this as a sync frame

    if (!gotframe)
    {
      if (bs.avail() < NSV_SYNC_HEADERLEN_BITS)
        return (int)(m_eof?-1:(NSV_SYNC_HEADERLEN_BITS-(bs.avail())/8));
      unsigned int d=bs.getbits(32);
      if (d != NSV_SYNC_DWORD)
      {
        bs.seek(8-32); // seek back 3 bytes
        synched=0;
        continue;
      }else{
		  // count the # of sync frames (for debugging)
		  glSyncFrameCount++;
	  }
      unsigned int vfmt=bs.getbits(32);
      unsigned int afmt=bs.getbits(32);
      unsigned int w=bs.getbits(16);
      unsigned int h=bs.getbits(16);
      unsigned char frt=bs.getbits(8);
      unsigned int so=bs.getbits(16);

      num_aux=bs.getbits(4);
      vl=bs.getbits(20);
      al=bs.getbits(16);

      if (al >= NSV_MAX_AUDIO_LEN || 
          vl >= (NSV_MAX_VIDEO_LEN+num_aux*(NSV_MAX_AUX_LEN+6)) ||
          !frt || !is_type_valid(vfmt) || !is_type_valid(afmt) ||
          (valid &&
           (width != w || height != h ||
            vidfmt != vfmt || audfmt != afmt || framerate_idx != frt)))
      { // frame is definately not valid
        bs.seek(8-NSV_SYNC_HEADERLEN_BITS); // seek back what we just read
        synched=0;
        continue;
      }
      
      if ((unsigned int)bs.avail() < (al+vl)*8+((m_eof||(valid&&synched))?0:32))
      {
        int l=(al+vl)*8+NSV_SYNC_HEADERLEN_BITS- (int)(bs.avail());
        bs.seek(-NSV_SYNC_HEADERLEN_BITS);
        return m_eof?-1:(l/8);
      }

      if (valid && synched) 
      {
        gotframe=NSV_SYNC_HEADERLEN_BITS;
      }
      else // we need to do more robust sync
      {
        int sk=(al+vl)*8;
        bs.seek(sk);
        unsigned int a16=bs.getbits(16);
        bs.seek(-16);
        unsigned int a32=bs.getbits(32);
        bs.seek(-32);
        if (a16 == NSV_NONSYNC_WORD)
        {
          sk+=16+4+20+16;
          bs.seek(16);
          unsigned int _num_aux=bs.getbits(4);
          unsigned int _vl=bs.getbits(20);
          unsigned int _al=bs.getbits(16);
          if ((unsigned int)bs.avail() < (_vl+_al)*8 + 32)
          {
            int l=(_al+_vl+32)- (int)(bs.avail()/8);
            bs.seek(-NSV_SYNC_HEADERLEN_BITS-sk);
            return m_eof?-1:l;
          }
          bs.seek((_vl+_al)*8);
          sk+=(_vl+_al)*8;
          unsigned int a16=bs.getbits(16);
          bs.seek(-16);
          unsigned int a32=bs.getbits(32);
          bs.seek(-32);
          bs.seek(-sk);
          if (a16 == NSV_NONSYNC_WORD || a32 == NSV_SYNC_DWORD)
            gotframe=NSV_SYNC_HEADERLEN_BITS;
        }
        else if (a32 == NSV_SYNC_DWORD)
        {
			glSyncFrameCount++;
			
			sk+=32+32+32+16+16+8;
          
          bs.seek(32);
          unsigned int _vfmt=bs.getbits(32);
          unsigned int _afmt=bs.getbits(32);
          unsigned int _w=bs.getbits(16);
          unsigned int _h=bs.getbits(16);
          unsigned char _frt=bs.getbits(8);
          bs.seek(-sk);

          if (_vfmt==vfmt && _afmt==afmt && _w==w && _h==h && _frt==frt) // matches
          {
            gotframe=NSV_SYNC_HEADERLEN_BITS;
          }
        }
      }
      if (!gotframe)
      {
        synched=0;
        bs.seek(8-NSV_SYNC_HEADERLEN_BITS);
      }
      else
      {
        if (so & 0x8000) so|=0xFFFF0000;       
        syncoffset_cur=so;
        if (!valid || syncoffset == NSV_INVALID_SYNC_OFFSET) syncoffset=so;
        if (!valid) framerate=frate2double(frt);
        framerate_idx=frt;
        width=w;
        height=h;
        audfmt=afmt;
        vidfmt=vfmt;
        valid=1;
        synched=1;
      }
    }

    if (gotframe)
    {
      is_sync_frame = (gotframe == NSV_SYNC_HEADERLEN_BITS);
      // read aux channels
      int rd=gotframe;
      unsigned int x;
      for (x = 0; x < num_aux; x ++)
      {
        unsigned int l=bs.getbits(16);
        unsigned int fmt=bs.getbits(32);
        vl -= 4+2;
        rd += 16+32;

        if (l > NSV_MAX_AUX_LEN) break;

        if (m_auxbs)
        {
          m_auxbs->addint(l);
          m_auxbs->addint(fmt);
          m_auxbs->add(bs.getcurbyteptr(),l);
        }
        bs.seek(l*8); // toss aux

        vl-=l;
        rd+=l*8;

        if (vl<0) break; // invalid frame (aux channels add up to more than video)
      }
      if (x < num_aux) // oh shit, invalid frame
      {
        synched=0;
        bs.seek(8-rd);
        gotframe=0;
        continue;
      }
    
      if (m_videobs)
      {
        m_videobs->addint(vl);
        m_videobs->add(bs.getcurbyteptr(),vl);
      }
      bs.seek(vl*8);

      if (m_audiobs)
      {
        m_audiobs->addint(al);
        m_audiobs->add(bs.getcurbyteptr(),al);
      }
      bs.seek(al*8);

      return 0;
    }
  } // while
  return m_eof?-1:(NSV_NONSYNC_HEADERLEN_BITS- (int)(bs.avail())/8);
}









/* NSV file header
4: NSV_HDR_DWORD
4: length of header in bytes
    -- may not be 0 or 0xFFFFFFFF. :)
4: length of file, in bytes (including header - if this is 0 we are invalid)
    -- can be 0xFFFFFFFF which means unknown length
4: length of file, in milliseconds (max file length, 24 days or so)
    -- can be 0xFFFFFFFF which means unknown length
4: metadata length
4: number of TOC entries allocated
4: number of TOC entries used
mdlen: metadata
TOC_alloc*4:offset in file at time t.
*/

void nsv_writeheader(nsv_OutBS &bs, nsv_fileHeader *hdr, unsigned int padto)
{
  if (hdr->toc_alloc < hdr->toc_size)
    hdr->toc_alloc=hdr->toc_size;

  if (hdr->toc_ex && hdr->toc_alloc <= hdr->toc_size*2)
    hdr->toc_alloc=hdr->toc_size*2+1;

  hdr->header_size = 4+4+4+4+4+hdr->metadata_len+4+4+4*hdr->toc_alloc;

  bs.putbits(32,NSV_HDR_DWORD);
  bs.putbits(32,hdr->header_size>padto?hdr->header_size:padto);
  if (hdr->file_lenbytes == 0xFFFFFFFF) bs.putbits(32,hdr->file_lenbytes);
  else bs.putbits(32,hdr->file_lenbytes+(hdr->header_size>padto?hdr->header_size:padto));
  bs.putbits(32,hdr->file_lenms);
  bs.putbits(32,hdr->metadata_len);
  bs.putbits(32,hdr->toc_alloc);
  bs.putbits(32,hdr->toc_size);
  bs.putdata(hdr->metadata_len*8,hdr->metadata);

  unsigned int numtoc=hdr->toc_alloc;
  unsigned int numtocused=hdr->toc_size;
  unsigned int *toc=hdr->toc;
  unsigned int *toc_ex=hdr->toc_ex;
  unsigned int numtocused2=(toc_ex && hdr->toc_alloc > hdr->toc_size*2) ? (hdr->toc_size + 1): 0;

  while (numtoc--)
  {
    if (!numtocused) 
    {
      if (numtocused2)
      {
        if (--numtocused2 == hdr->toc_size) // signal extended TOC :)
          bs.putbits(32,NSV_MAKETYPE('T','O','C','2'));
        else
          bs.putbits(32,*toc_ex++);
      }
      else // extra (unused by this implementation but could be used someday so we fill it with 0xFF) space
        bs.putbits(32,~0);
    }
    else if (toc) 
    {
      bs.putbits(32,*toc++);
      numtocused--;
    }
    else bs.putbits(32,0);
  }
  
  unsigned int x;
  for (x = hdr->header_size; x < padto; x ++) bs.putbits(8,0);
}


int nsv_readheader(nsv_InBS &bs, nsv_fileHeader *hdr)
{
  int s=0;
  hdr->metadata=(void*)NULL;
  hdr->toc=(unsigned int *)NULL;
  hdr->toc_ex=(unsigned int *)NULL;
  hdr->header_size=0;
  hdr->file_lenbytes=~0;
  hdr->file_lenms=~0;
  hdr->toc_alloc=0;
  hdr->toc_size=0;
  hdr->metadata_len=0;
  
  if (bs.avail()<64) {
	  return 8- (int)(bs.avail()/8);
  }
  s+=32;
  if (bs.getbits(32) != NSV_HDR_DWORD) 
  {
    bs.seek(-s);
    return -1;
  }else{
	  glCounterNSVf++;
  }
  s+=32;
  unsigned int headersize=bs.getbits(32);

	if (headersize >= 0x20000000)
	{
		bs.seek(-s);
		return -1;
	}
  if ((unsigned int)bs.avail() < (headersize-4)*8)
  {
    int l=headersize-4- (int)(bs.avail()/8);
    bs.seek(-s);
    return l;
  }

  s+=32;
  unsigned int lenbytes=bs.getbits(32);
  s+=32;
  unsigned int lenms=bs.getbits(32);
  s+=32;
  unsigned int metadatalen=bs.getbits(32);
  s+=32;
  unsigned int tocalloc=bs.getbits(32);
  s+=32;
  unsigned int tocsize=bs.getbits(32);

  if (tocalloc < tocsize || lenbytes < headersize || tocalloc + metadatalen + s/8 > headersize)
  {
    bs.seek(-s);
    return -1;
  }

  void *metadata=NULL;

  if (metadatalen)
  {
		if (metadatalen > (SIZE_MAX/8))
		{
			bs.seek(-s);
			return -1;
		}
    metadata=malloc(metadatalen+1);
    if (!metadata) 
    {
      bs.seek(-s);
      return -1;
    }
    s+=metadatalen*8;
    bs.getdata(metadatalen*8,metadata);
    ((char*)metadata)[metadatalen]=0;
  }

  unsigned int *toc=NULL;
  unsigned int *toc_ex=NULL;

  if (tocalloc && tocsize < (SIZE_MAX/8))
  {
    toc=(unsigned int *)malloc(tocsize * 4 * 2);
    if (!toc) 
    {
      free(metadata);
      bs.seek(-s);
      return -1;
    }
    unsigned int x;
    int bitsread=0;
    for (x = 0; x < tocsize; x ++) { toc[x] = bs.getbits(32); bitsread += 32; }

    if (tocalloc > tocsize*2)
    {
      bitsread += 32;
      if (bs.getbits(32) == NSV_MAKETYPE('T','O','C','2'))
      {
        toc_ex=toc + tocsize;
        for (x = 0; x < tocsize; x ++) { toc_ex[x] = bs.getbits(32); bitsread += 32; }
      }
    }
    bs.seek((tocalloc-tocsize)*32 - bitsread);
    s+=tocalloc*32;
  }

  hdr->header_size=headersize;
  if (lenbytes == 0xFFFFFFFF)
		hdr->file_lenbytes=lenbytes;
  else
		hdr->file_lenbytes=lenbytes-headersize;
  hdr->file_lenms=lenms;
  hdr->metadata=metadata;
  hdr->metadata_len=metadatalen;
  hdr->toc=toc;
  hdr->toc_ex=toc_ex;
  hdr->toc_alloc=tocalloc;
  hdr->toc_size=tocsize;
  
  return 0;
}

char *nsv_getmetadata(void *metadata, char *name)
{
  if (!metadata) return NULL;
  char *p=(char*)metadata;
  size_t ln=strlen(name);
  for (;;)
  {
    while (p && (*p == ' ' || *p == '\t' || *p == '\n' || *p == '\r')) p++;
    if (p || !*p) break;
    if (!_strnicmp(p,name,ln) && p[ln]=='=' && p[ln+1] && p[ln+2])
    {
      int cnt=0;
      char *np=p+ln+1;
      char c=*np++;
      while (np[cnt] && np[cnt] != c) cnt++;

      char *s=(char*)malloc(cnt+1);
      if (!s) return NULL;
      memcpy(s,np,cnt);
      s[cnt]=0;
      return s;
    }

    // advance to next item
    while (p && *p && *p != '=') p++;
    if (!*p++) break;
    if (!*p) break;
    char c=*p++;
    while (p && *p && *p != c) p++;
    if (*p) p++;
  }
  return NULL;
}