#define _FILE_OFFSET_BITS 64
#define _LARGEFILE_SOURCE
#define _LARGEFILE64_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include <fcntl.h>
#include <sys/time.h>
#include <unistd.h>
#include <math.h>
#include <time.h>
#include <errno.h>
#include <sys/ioctl.h>
#include <sys/user.h>
#include <sys/poll.h>


#include "ringbuff.h"
#include "device.h"
#include <unistd.h>
#include <fcntl.h>
#include <stdlib.h>
#include <sys/ioctl.h>
#include <vdr/dvbspu.h>

extern "C" {
#include "videodev.h"
#include "videodev2.h"

#include "ivtv-ext-api.h"
}
#include "blank.h"
#include "format.h"


#define UNSYNCED 0
#define PAYLOAD 100
#define PAYLOADDATA 200
#define HEADER 300
#define OPTHEADER 400
#define STREAM 500


static cPvr350Device  *Instance = 0;

cPvr350Device::cPvr350Device(void)
    : fd_out(-1)
{
    Instance = this;
    Open();
}


void cPvr350Device::Open()
{
    if ( fd_out != -1 ) {
        return;
    }

    fd_out = open("/dev/video16", O_WRONLY); // |O_NONBLOCK);

    if ( fd_out < 0 ) {
        printf("Can't open device\n");
    }
    int fbno;
    char  fbdev[FILENAME_MAX+1];
    if (ioctl(fd_out, IVTV_IOC_GET_FB, &fbno) < 0) {
        perror("IVTV_IOC_GET_FB");
        return ;
    }
    
    if (fbno < 0) {
        printf("invalid fb, are you using the ivtv-fb module?\n");
        return ;
    }

    Format16_9 = false;

    snprintf(fbdev,sizeof(fbdev),"/dev/fb%d",fbno);
    if ( (fbfd = open(fbdev,O_RDWR)) > 0 ) {

        struct ivtvfb_ioctl_state_info fbstate;
        memset(&fbstate, 0, sizeof(fbstate));

        if (ioctl(fbfd, IVTVFB_IOCTL_GET_STATE, &fbstate) < 0) {
            perror("IVTVFB_IOCTL_GET_STATE");
            return;
        }

        initglobalalpha = fbstate.status;
        storedglobalalpha = fbstate.alpha;

        fbstate.status &= ~IVTVFB_STATUS_GLOBAL_ALPHA;
        fbstate.status |= IVTVFB_STATUS_LOCAL_ALPHA;
        fbstate.alpha = 0;

        if (ioctl(fbfd, IVTVFB_IOCTL_SET_STATE, &fbstate) < 0) {
            perror("IVTVFB_IOCTL_SET_STATE");
            return ;
        }

        struct ivtvfb_ioctl_get_frame_buffer igfb;
        memset(&igfb, 0, sizeof(igfb));

        ioctl(fbfd, IVTVFB_IOCTL_GET_FRAME_BUFFER, &igfb);

        osdbufsize = igfb.size;
        stride = igfb.sizex * 4;
        printf("sizex %d sizey %d size %d\n",igfb.sizex,igfb.sizey,igfb.size);
        printf("Size %d %d\n",stride,osdbufsize + PAGE_SIZE);

        osdbuffer = new unsigned char[osdbufsize + PAGE_SIZE];
        osdbuf_aligned = (unsigned char *)((int)osdbuffer + (PAGE_SIZE - 1));
        osdbuf_aligned = (unsigned char *)((int)osdbuf_aligned & PAGE_MASK);

        memset(osdbuf_aligned, 0x00, osdbufsize);
#if 1
        struct ivtv_osd_coords osdcoords;
        memset(&osdcoords, 0, sizeof(osdcoords));
        ioctl(fbfd, IVTVFB_IOCTL_GET_ACTIVE_BUFFER, &osdcoords);

        struct ivtvfb_ioctl_dma_host_to_ivtv_args prep;
        memset(&prep, 0, sizeof(prep));

        prep.source = osdbuf_aligned;
        prep.dest_offset = 0;
        prep.count = osdcoords.max_offset;

        memset(osdbuf_aligned, 0x00, osdbufsize);

        ioctl(fbfd, IVTVFB_IOCTL_PREP_FRAME, &prep);
        printf("Offset %d max %d stride %d lines %d x %d y %d\n",osdcoords.offset,osdcoords.max_offset,
               osdcoords.pixel_stride,osdcoords.lines,osdcoords.x,osdcoords.y);

 
        osdcoords.lines = 576;
        osdcoords.offset = 0;
        osdcoords.pixel_stride = 720 * 2;

        ioctl(fbfd, IVTVFB_IOCTL_SET_ACTIVE_BUFFER, &osdcoords);
#endif

    } else {
        printf("Can't open framebuffer..\n");
    }


    struct v4l2_control ctrl;
    int ret;
    memset(&ctrl, 0, sizeof(ctrl));

    ctrl.id = V4L2_CID_IVTV_DEC_PREBUFFER;
    ctrl.value = 0;

    if ( ( ret =ioctl(fd_out, VIDIOC_S_CTRL, &ctrl) ) < 0 ) {
        printf("1: %d\n",ret);
    }

    ctrl.id = V4L2_CID_IVTV_DEC_NUM_BUFFERS;
    ctrl.value = 0;

    if ( ( ret =ioctl(fd_out, VIDIOC_S_CTRL, &ctrl) ) < 0 ) {
        printf("2: %d\n",ret);
    }

    mute = 0;
    spuDecoder = NULL;
    ResetVideoSize();
    
    /* Create a new ringbuffer for queuing data up into, and create
       the writing thread
    */
    state = UNSYNCED;
    m_RingBuffer = new cRingBuffLinear(3000 * 512, 0, true);
//  m_RingBuffer = new cRingBuffLinear(3000 * 1024, 0, true);
    writer = new cPvr350Writer(m_RingBuffer,fd_out);
}

void cPvr350Device::Close()
{
    delete writer;
    delete m_RingBuffer;

    if ( osdbuffer != NULL ) {
        delete[] osdbuffer;
    }

    if ( fd_out != -1 ) {
        close(fd_out);
        fd_out = -1;
    }

    if ( fbfd != -1 ) {
        close(fbfd);
        fbfd = -1;
    }

}

cPvr350Device::~cPvr350Device()
{
    Close();
}

void cPvr350Device::MakePrimaryDevice(bool On)
{
#if VDRVERSNUM >= 10307
    new cPvr350OsdProvider(fbfd, osdbuffer);
#endif
}


bool cPvr350Device::HasDecoder(void) const
{
    return true; // We can decode MPEG2
}

bool cPvr350Device::CanReplay(void) const 
{
    return true;  // We can replay
}

int cPvr350Device::ProvidesCa(int Ca) 
{
    return 0;
}


bool cPvr350Device::SetPlayMode(ePlayMode PlayMode)
{ 
    int          ret;
    m_PlayMode = PlayMode;

    switch ( PlayMode ) {
    case pmNone:
        Open();
        struct ivtv_ioctl_fwapi   fwcall;

        fwcall.cmd = 0x02;
        fwcall.args = 1;
        fwcall.data[0] = 1;

        Clear();
        if ((ret = ioctl(fd_out, IVTV_IOC_FWAPI, &fwcall)) < 0) {
            printf("STOP_DECODE %d\n",ret);
        }
        if ( ( ret = ioctl(fd_out, IVTV_IOC_DEC_FLUSH, 0) ) < 0 ) {
            printf("4: %d\n",ret);
        }
        Clear();
        break;
    case  pmExtern_THIS_SHOULD_BE_AVOIDED:
        Close();
        break;
    case pmAudioOnlyBlack:
    default:    
        Open();
        PlayVideo(blank,sizeof(blank));
        ivtv_cfg_start_decode startd;
        Clear();

        memset(&startd, 0, sizeof(startd));
        if ( ( ret = ioctl(fd_out, IVTV_IOC_S_START_DECODE, &startd) ) <  0 ) {
            printf("3: %d\n",ret);
        }
        if ( ( ret = ioctl(fd_out, IVTV_IOC_PLAY, 0) ) < 0 ) {
            printf("5: %d\n",ret);
        }
    }
    return true;
}

/* Stop playback ensuring last frame is visible, clear ringy and
 * then start playing again - VDR does all the frame skipping for
 * us which is handy
 */
void cPvr350Device::TrickSpeed(int Speed) 
{
    struct ivtv_ioctl_fwapi   fwcall;
    int        ret;

    fwcall.cmd = 0x02;
    fwcall.args = 1;
    fwcall.data[0] = 0;        /* Display last frame */

    Clear();
    if ((ret = ioctl(fd_out, IVTV_IOC_FWAPI, &fwcall)) < 0) {
        printf("STOP_DECODE %d\n",ret);
    }
    Clear();
    SetPlayMode(m_PlayMode);
}


void cPvr350Device::SetVideoFormat(bool VideoFormat16_9)
{
    Format16_9 = VideoFormat16_9;
}


void cPvr350Device::Clear(void) 
{
    m_RingBuffer->Clear();
}

void cPvr350Device::Play(void) 
{
    ioctl(fd_out, IVTV_IOC_PLAY, 0);
}

void cPvr350Device::Freeze(void) 
{    
    ioctl(fd_out, IVTV_IOC_PAUSE, 0);
}

void cPvr350Device::Mute(void) 
{
}

/* This bit of code is taken from softdevice - simply split up the audio/video
   streams to find out the aspect ratio that's needed
*/
#if VDRVERSNUM >= 10318
int cPvr350Device::PlayAudio(const uchar *Data, int Length, uchar Id)
{
	return PlayVideo(Data,Length);
}
#endif

int cPvr350Device::PlayVideo(const uchar *Data, int Length)
{
    int len, streamlen;
    uint8_t *inbuf_ptr;                                 
    inbuf_ptr = (uint8_t *)Data;
    int size=Length;
  
    while (size > 0) {
        len=1;
        switch (state) {
        case UNSYNCED:
            syncword = (syncword << 8) | *inbuf_ptr;
            if ( (syncword >= 0x000001E0) && (syncword <= 0x000001EF) ) {
                //    printf("Sync word is %x\n",syncword);
                state=PAYLOAD;
                streamtype= 0xe0;
                SetVidInfo(inbuf_ptr,size);
            } else if ( (syncword == 0x000001C0)  && (syncword <= 0x000001CF) ) {
                state=PAYLOAD;
                streamtype= 0xC0; // syncword- 0x00000100;
            } else if ( syncword == 0x000001bd)  {  /* ac3 */
                state=PAYLOAD;
                streamtype= 0xC0; // syncword- 0x00000100;
               
            }
            break;
        case PAYLOAD:                //Payload length(hi)
            payload = *inbuf_ptr << 8;
            state++;
            break;
        case PAYLOAD+1:                //Payload length(lo)
            payload += *inbuf_ptr;
            header[0]=0;
            header[1]=0;
            header[2]=1;
            header[3]=streamtype;
            header[4]=payload >> 8;
            header[5]=payload & 0xFF;
            m_RingBuffer->Put(header,6);           
            state=PAYLOADDATA;
            break;
        case PAYLOADDATA:
            streamlen = min(payload,size);
            m_RingBuffer->Put(inbuf_ptr,streamlen);           
            payload-=streamlen; 
            len = streamlen;
            if ( payload <= 0 ) {
                state = UNSYNCED;
                syncword = 0;
            }
            break;
        }
        size -= len;
        inbuf_ptr += len;
    }
    return Length;
}

void cPvr350Device::SetVolumeDevice(int Volume)
{
}

void cPvr350Device::StillPicture(const uchar *Data, int Length) 
{
#if 0
#define MIN_IFRAME 400000
    for (int i = MIN_IFRAME / Length + 1; i > 0; i--) {
        safe_write(fd_out, Data, Length);
        usleep(1); // allows the buffer to be displayed in case the progress display is active
    }
#endif

}

bool cPvr350Device::Poll(cPoller &Poller, int TimeoutMs) 
{
    Poller.Add(fd_out,true);
    return Poller.Poll(TimeoutMs);
}


cSpuDecoder *cPvr350Device::GetSpuDecoder(void)
{
    if (!spuDecoder && IsPrimaryDevice())
        spuDecoder = new cDvbSpuDecoder();
    return spuDecoder;
}

#if VDRVERSNUM < 10307
cOsdBase *cPvr350Device::NewOsd(int x, int y)
{
    return new cPvr350Osd(fbfd,osdbuffer,x,y);
}
#endif


void cPvr350Device::SetVidInfo(const uchar *mbuf, int count)
{
    const uchar *headr;
    int found = 0;
    int sw;
    int c = 0;
    struct ivtv_ioctl_fwapi   fwcall;
    int   ret;

    fwcall.cmd = 0x56;
    fwcall.result = 0x00;
    fwcall.args = 4;
    fwcall.data[2] = fwcall.data[3] = 0;

    while (found < 4 && c+4 < count){
        const uchar *b;

        b = mbuf+c;
        if ( b[0] == 0x00 && b[1] == 0x00 && b[2] == 0x01
             && b[3] == 0xb3) found = 4;
        else {
            c++;
        }
    }

    if (! found) return;
    c += 4;
    if (c+12 >= count) return;
    headr = mbuf+c;


    int horizontal_size        = ((headr[1] &0xF0) >> 4) | (headr[0] << 4);
    int vertical_size        = ((headr[1] &0x0F) << 8) | (headr[2]);
    sw = (int)((headr[3]&0xF0) >> 4) ;
    
    
    switch ( sw ) {
    case 1:
        break;
    case 2:  /* 4:3 */
#if 0
        if ( horizontal_size <= vertical_size) {
            fwcall.data[0] = width;
            fwcall.data[1] = height;
            fwcall.data[2] = lx;
            fwcall.data[3] = ly;
        } else {
            fwcall.data[0] = width < horizontal_size ? width : horizontal_size;
            fwcall.data[1] = height < vertical_size ? height : vertical_size;
            fwcall.data[2] = lx + (width - fwcall.data[0]) / 2;
            fwcall.data[3] = ly + (height - fwcall.data[1]) / 2;
        }
#else
        if ( Format16_9 == false ) {
            fwcall.data[0] = width;
            fwcall.data[1] = height;
            fwcall.data[2] = lx;
            fwcall.data[3] = ly;
        } else {
            /* TODO: 16_9 format, guess we vertical letter box? */
        }
#endif

        if ( fwcall.data[0] != current_horiz ||
             fwcall.data[1] != current_vertical ) {
            current_horiz = fwcall.data[0];
            current_vertical = fwcall.data[1];
            if ( ( ret = ioctl(fd_out,IVTV_IOC_FWAPI,&fwcall) ) < 0 ) {
                printf("size set\n");
            }
        }
        break;
    case 3:  /* 16:9 */
        if ( Format16_9 ) {  /* 16:9 TV */
            fwcall.data[0] = width;
            fwcall.data[1] = height;
            fwcall.data[2] = lx;
            fwcall.data[3] = ly;
        } else {   /* 4:3 TV */
            if ( horizontal_size <= vertical_size) {
                fwcall.data[0] = width;
                fwcall.data[1] = (width/ 16) * 9;
                fwcall.data[2] = lx + (width - fwcall.data[0]) / 2;
                fwcall.data[3] = ly + (height- fwcall.data[1]) / 2;
            } else {
                fwcall.data[0] = width < horizontal_size ? width : horizontal_size;
                fwcall.data[1] = ( fwcall.data[0] ) / 16 * 9;
                fwcall.data[2] = lx + (width - fwcall.data[0]) / 2;
                fwcall.data[3] = ly + (height - fwcall.data[1]) / 2;
            }
        }

        if ( fwcall.data[0] != current_horiz ||
             fwcall.data[1] != current_vertical || sizechanged ) {
	    sizechanged = 0;
            current_horiz = fwcall.data[0];
            current_vertical = fwcall.data[1];
            if ( ( ret = ioctl(fd_out,IVTV_IOC_FWAPI,&fwcall) ) < 0 ) {
                printf("size set\n");
            }
	    printf("Change: %ld %ld %ld %ld\n",fwcall.data[0],fwcall.data[1],fwcall.data[2],fwcall.data[3]);
        }
        break;
    case 4:
        break;                    
    case 5 ... 15:
        break;
    default:
        break;
    }
}

int64_t cPvr350Device::GetSTC(void)
{
    return -1;
}

void cPvr350Device::SetVideoSize(int x, int y, int w, int d)
{
	printf("Video window %d,%d %d,%d\n",x,y,w,d);
	width = w;
	height = d;
	lx = x;
	ly = y;
	sizechanged = 1;
}

void SetVideoSize(int x, int y, int w, int d)
{
    Instance->SetVideoSize(x,y,w,d);
}

void ResetVideoSize()
{
    /* FIXME: Think NTSC */
    Instance->SetVideoSize(0,0,720,576);
}
