/***************************************************************************
    copyright            : (C) 2007/2009 by mean
    email                : fixounet@free.fr
    

 ***************************************************************************/

/***************************************************************************
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 ***************************************************************************/

#include "ADM_default.h"
#include "fourcc.h"
#include <math.h>
#include "DIA_coreToolkit.h"
#include "ADM_indexFile.h"
#include "ADM_ts.h"
#include "ADM_videoInfoExtractor.h"
#include "prefs.h"

#define MY_CLASS tsHeader
#include "ADM_coreDemuxerMpegTemplate.cpp.h"

extern uint8_t tsIndexer(const char *file, bool memOnly);

/**
    \fn open
    \brief Wrapper for openInternal to query preferences in a single location.
*/
uint8_t tsHeader::open(const char *name)
{
    ADM_indexingType idxt = ADM_IDX_WRITE_TO_DISK;
    uint32_t indexingFlags = ADM_IDX_FLAGS_DEFAULT;

    if (prefs->get(INDEXING_INDEXING_FLAGS, &indexingFlags))
    {
        indexingFlags >>= ADM_IDX_FLAGS_OFFSET_MPEGTS;
        if (!(indexingFlags & ADM_IDX_FLAG_WRITE_INDEX_FILE))
        {
            idxt = ADM_IDX_USE_EXISTING;
        }
        if (indexingFlags & ADM_IDX_FLAG_IGNORE_INDEX_FILE)
        {
            idxt = ADM_IDX_MEMFILE_ONLY;
            ADM_info("Mem-only indexing.\n");
        }
    }

    return openInternal(name, idxt);
}

/**
    \fn openInternal
    \brief Open given MPEG-TS file, gather infos and build index(es).
*/
uint8_t tsHeader::openInternal(const char *name, ADM_indexingType &strategy)
{
    char *idxName=(char *)malloc(strlen(name)+6);
    uint8_t r=1;

    sprintf(idxName,"%s.idx2",name);
    ListOfIndexFiles.push_back(idxName);

    if(strategy == ADM_IDX_MEMFILE_ONLY || !ADM_fileExist(idxName))
        r = tsIndexer(name, strategy != ADM_IDX_WRITE_TO_DISK);
    if(r!=ADM_OK)
    {
        if(r==ADM_IGN)
            ADM_warning("Indexing cancelled by the user, deleting the index file. Bye.\n");
        if(!r)
            ADM_error("Indexing of %s failed, aborting\n",name);
        if(strategy == ADM_IDX_WRITE_TO_DISK && ADM_fileExist(idxName) && !ADM_eraseFile(idxName))
            ADM_warning("Could not delete %s\n",idxName);
        free(idxName);
        return r;
    }

    char *type;
    uint64_t startDts;
    uint32_t version=0;
    bool reindex=false;
    int append;
    r=0;

    indexFile index;
    if(!index.open(idxName, strategy == ADM_IDX_MEMFILE_ONLY))
    {
        printf("[tsDemux] Cannot open %s file %s\n", idxName, (strategy == ADM_IDX_MEMFILE_ONLY) ? "in-memory index" : "index", idxName);
        free(idxName);
        return false;
    }
    if(!index.readSection("System"))
    {
        printf("[tsDemux] Cannot read system section\n");
        goto abt;
    }
    type=index.getAsString("Type");
    if(!type || type[0]!='T')
    {
        printf("[tsDemux] Incorrect or not found type\n");
        goto abt;
    }

    version=index.getAsUint32("Version");
    if(version!=ADM_INDEX_FILE_VERSION)
    {
        ADM_warning("Index file version mismatch, expected %" PRIu32", got %" PRIu32"\n", ADM_INDEX_FILE_VERSION, version);
        if (strategy == ADM_IDX_MEMFILE_ONLY)
        {
            ADM_error("Index file version mismatch despite in-memory indexing, bailing out.\n");
            goto abt;
        }
        if(GUI_Question(QT_TRANSLATE_NOOP("tsdemuxer","This file's index has been created with an older version of avidemux.\nThe file must be re-indexed. Proceed?")))
            reindex=true;
        goto abt;
    }
    append=index.getAsUint32("Append");
    ADM_assert(append>=0);
    printf("[tsDemux] Append=%d\n",append);
    if(!parser.open(name,&append))
    {
        printf("[tsDemux] Cannot open root file (%s)\n",name);
        goto abt;
    }
    if(!readVideo(&index)) 
    {
        printf("[tsDemux] Cannot read Video section of %s\n",idxName);
        goto abt;
    }

    if(!readAudio(&index,name)) 
    {
        printf("[tsDemux] Cannot read Audio section of %s => No audio\n",idxName);
    }

    if(!readIndex(&index))
    {
        printf("[tsDemux] Cannot read index for file %s\n",idxName);
        goto abt;
    }
    if(!ListOfFrames.size())
    {
        ADM_info("[TSDemux] No video frames\n");
        goto abt;
    }
    updateIdr();
    updatePtsDts();
    _videostream.dwLength= _mainaviheader.dwTotalFrames=ListOfFrames.size();
    printf("[tsDemux] Found %d video frames\n",_videostream.dwLength);
    if(_videostream.dwLength)_isvideopresent=1;
//***********
    
    tsPacket=new tsPacketLinear(videoPid);
    if(tsPacket->open(name,append)==false)
    {
        printf("tsDemux] Cannot tsPacket open the file\n");
        goto abt;
    }
    r=true;
    for(int i=0;i<listOfAudioTracks.size();i++)
    {
        ADM_tsTrackDescriptor *desc=listOfAudioTracks[i];
        ADM_audioStream *audioStream=ADM_audioCreateStream(&desc->header,desc->access);
        if(!audioStream)
        {
            
        }else       
        {
                desc->stream=audioStream;
                audioStream->setLanguage(desc->language);
        }
    }
abt:
    index.close();
    if (reindex && strategy != ADM_IDX_MEMFILE_ONLY)
    {
        uint8_t success = 1;
        if (strategy == ADM_IDX_WRITE_TO_DISK)
            success = ADM_eraseFile(idxName);
        else
            strategy = ADM_IDX_MEMFILE_ONLY;
        free(idxName);
        if (success)
        {
            r = openInternal(name, strategy);
        } else
        {
            ADM_error("Can't delete old index file.\n");
        }
    } else
    {
        free(idxName);
    }
    printf("[tsDemuxer] open() returned %d\n",r);
    return r;
}

/**
    \fn updateIdr
    \brief if IDR are present, handle them as intra and I as P frame
            if not, handle I as intra, there might be some badly decoded frames (missing ref)
*/
bool tsHeader::updateIdr()
{
    int nbIdr=0;
    int nbI=0,nbP=0,nbB=0;
    if(!ListOfFrames.size()) return false;
    for(int i=0;i<ListOfFrames.size();i++)
    {
        int type=ListOfFrames[i]->type;
        switch(type)
        {
            case 1: nbI++;break;
            case 2: nbP++;break;
            case 3: nbB++;break;
            case 4: nbIdr++;break;
            default:
                    ADM_assert(0);
                    break;
        }
    }
    printf("[TsDemuxer] Found %d I, %d B, %d P\n",nbI,nbB,nbP);
    printf("[TsH264] Found %d IDR\n",nbIdr);
#if 0
    if(nbIdr>1) // Change IDR to I and I to P...
    { 
        printf("[TsH264] Remapping IDR to I and I TO P\n");
        for(int i=0;i<ListOfFrames.size();i++)
        {
            switch(ListOfFrames[i]->type)
            {
                case 4: ListOfFrames[i]->type=1;break;
                case 1: 
                        if(i)
                            ListOfFrames[i]->type=2;break;
                default: break;
            }
        }
    }else // I & IDR are all keyframes
    {
        for(int i=0;i<ListOfFrames.size();i++)
        {
            if(ListOfFrames[i]->type==4)
                ListOfFrames[i]->type=1;
        }
        
    }
#endif
    return true;
}

/*
    __________________________________________________________
*/

void tsHeader::Dump(void)
{
 
}
/**
    \fn close
    \brief cleanup
*/

uint8_t tsHeader::close(void)
{
    ADM_info("Closing TS demuxer\n");
    // Destroy index
    int n=ListOfFrames.size();
    for(int i=0;i<n;i++)
        delete ListOfFrames[i];
    ListOfFrames.clear();

    // Destroy audio tracks
    n=listOfAudioTracks.size();
    for(int i=0;i<n;i++)
    {
        ADM_tsTrackDescriptor *desc=listOfAudioTracks[i];
        delete desc;
        listOfAudioTracks[i]=NULL;
    } // Container will be destroyed by vector destructor
    listOfAudioTracks.clear();
    if(tsPacket)
    {
        tsPacket->close();
        delete tsPacket;
        tsPacket=NULL;
    }
    
    n = ListOfIndexFiles.size();
    for (int i=0; i<n; i++)
    {
        mfcleanup(ListOfIndexFiles.back());
        ListOfIndexFiles.pop_back();
    }
    
    return 1;
}
/**
    \fn tsHeader
    \brief constructor
*/
 tsHeader::tsHeader( void ) : vidHeader()
{
    tsPacket = NULL;
    fieldEncoded=false;
#define TS_INVALID 0xFFFFFFFF
    lastFrame = TS_INVALID;
    videoPid=0;
    videoNeedEscaping=false;
    sizeOfVideoInBytes=0;
    videoDuration = ADM_NO_PTS;
}
/**
    \fn tsHeader
    \brief destructor
*/

 tsHeader::~tsHeader(  )
{
    ADM_info("Destroying TS demuxer\n");
    close();
}

static bool isKeyFrame(uint8_t type)
{
    if (type == 1 || type == 4)
        return true;
    return false;
}

/**
        \fn getFrame
*/

#define UNESCAPE() if(r==true && 0 && videoNeedEscaping)  \
                    {\
                        uint32_t l=img->dataLength;\
                        uint32_t l2=ADM_unescapeH264(l,img->data, img->data);\
                        if(l!=l2)\
                            memset(img->data+l2,0,l-l2);\
                    }

uint8_t  tsHeader::getFrame(uint32_t frame,ADMCompressedImage *img)
{
    if(frame>=ListOfFrames.size()) return 0;
    dmxFrame *pk=ListOfFrames[frame];
    // next frame
    if((lastFrame != TS_INVALID) && (frame == lastFrame + 1) && !isKeyFrame(pk->type))
    {
        lastFrame++;
        bool r=tsPacket->read(pk->len,img->data);
             img->dataLength=pk->len;
             img->demuxerFrameNo=frame;
             img->demuxerDts=pk->dts;
             img->demuxerPts=pk->pts;
             //printf("[>>>] %d:%02x %02x %02x %02x\n",frame,img->data[0],img->data[1],img->data[2],img->data[3]);
             getFlags(frame,&(img->flags));
             UNESCAPE();
             return r;
    }
    // Intra ?
    if(isKeyFrame(pk->type))
    {
        if(!tsPacket->seek(pk->startAt,pk->index)) return false;
         bool r=tsPacket->read(pk->len,img->data);
             img->dataLength=pk->len;
             img->demuxerFrameNo=frame;
             img->demuxerDts=pk->dts;
             img->demuxerPts=pk->pts;
             getFlags(frame,&(img->flags));
             //printf("[>>>] %d:%02x %02x %02x %02x\n",frame,img->data[0],img->data[1],img->data[2],img->data[3]);
             lastFrame=frame;
             UNESCAPE();
             return r;

    }
    
    // Random frame
    // Need to rewind, then forward
    int startPoint=frame;
    while(startPoint > 0 && !isKeyFrame(pk->type))
    {
        startPoint--;
        pk = ListOfFrames[startPoint];
    }
    // ADM_info("Want frame %" PRIu32", rewinding to frame %" PRIu32", last frame was %" PRIu32"\n", frame, startPoint, lastFrame);
    if(!tsPacket->seek(pk->startAt,pk->index)) 
    {
            printf("[tsDemux] Failed to rewind to frame %" PRIu32"\n",startPoint);
            lastFrame = TS_INVALID;
            return false;
    }
    // Now forward
    while(startPoint<frame)
    {
        pk=ListOfFrames[startPoint];
        if(!tsPacket->read(pk->len,img->data))
        {
            printf("[tsDemux] Read fail for frame %" PRIu32"\n",startPoint);
            lastFrame = TS_INVALID;
            return false;
        }
        lastFrame=startPoint;
        startPoint++;
    }
    pk=ListOfFrames[frame];
    lastFrame++;
    bool r=tsPacket->read(pk->len,img->data);
         img->dataLength=pk->len;
         img->demuxerFrameNo=frame;
         img->demuxerDts=pk->dts;
         img->demuxerPts=pk->pts;
         //printf("[>>>] %d:%02x %02x %02x %02x\n",frame,img->data[0],img->data[1],img->data[2],img->data[3]);
         getFlags(frame,&(img->flags));
         UNESCAPE();
         return r;
}


//EOF
