/* Swfdec
 * Copyright (C) 2007-2008 Benjamin Otte <otte@gnome.org>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 * 
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, 
 * Boston, MA  02110-1301  USA
 */

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
#include "Locker.h"
#include "NMPGnashChannel.h"
#include "NMPPlayer.h"
#include "ipcchannel.h"
#include "swfdec_nmp.h"

typedef struct MediaPlayer
{
  MPSTATUS status;
  LockMutex status_mutex;
  NMPPlayer *player;
  NMPGnashChannel *ch;
  LockMutex size_mutex;
  int x, y, w, h; 
  int failflag;
  NMP_MEDIA_METATAG * mtag;
  double bitrate;
} MEDIAPLAYER;

int io_eof( void *opaque )
{
	return 0;
}

int io_error( void *opaque )
{
	return 0;
}

int io_dof( void *opaque )
{
	return 0;
}

int io_first_peek( void *opaque, char *buf, int size )
{
	int len  = 0;
	return len;
}

int io_read_callback( void *opaque, char *buf, int size )
{
	int len  = 0;
	return len;
}

unsigned long io_seek_callback( void *opaque, unsigned long offset )
{
	return 0;
}

int io_size_callback( void *opaque, unsigned long *sz)
{
	return 0;
}

int io_readysize_callback( void *opaque, unsigned long *sz )
{
	return 0;
}

int io_cancel ( void *opaque )
{
	return 0;
}

int notifyCb(NMP_NOTIFY notify_type, void *notify_data, void *user_data)
{
  printf("----> notify, type = %d\n", notify_type);
  switch (notify_type)
  {
	case NMP_NOTIFY_FINISHED:
		{
			swfdec_nmp_set_status(user_data, MP_PB_FINISHED);
		}
		break;
	case NMP_NOTIFY_BUF_PAUSE:
		{
			swfdec_nmp_set_status(user_data, MP_PB_BUFFERING);
		}
		break;
	case NMP_NOTIFY_BUF_PAUSE_OFF:
		{
			swfdec_nmp_set_status(user_data, MP_PB_PLAYING);
		}
		break;
	default:
		break;
  }
  return 0;
}

int swfdec_nmp_constructor(void **mp)
{
  MediaPlayer *p = NULL;
  p = new MediaPlayer;
  if (p == NULL)
	return -1;
  p->status = MP_CLOSED;
  p->failflag = 0;
  p->mtag = NULL;
  p->bitrate = 320000.0;
  p->player = NULL;
  p->ch = NULL;
  p->x = -1;
  p->y = -1;
  p->w = -1;
  p->h = -1;
  *mp = p;
  return 0;
}

int swfdec_nmp_destructor(void **mp)
{
  MediaPlayer *p = (MediaPlayer *)*mp;
  if (p == NULL)
	return -1;
  if (p->status > MP_CLOSED)
	swfdec_nmp_close(*mp);
  delete p;
  *mp = NULL;
  return 0; 
}

MPSTATUS swfdec_nmp_set_status(void *mp, MPSTATUS status)
{
  MediaPlayer *p = (MediaPlayer *)mp;

  if (p == NULL)
	return MP_CLOSED;
  MPSTATUS old = MP_CLOSED;
  Locker locker(&p->status_mutex);
  old = p->status;
  p->status = status;
  return old;
}

MPSTATUS swfdec_nmp_get_status(void *mp)
{
  MediaPlayer *p = (MediaPlayer *)mp;

  if (p == NULL)
	return MP_CLOSED;
  Locker locker(&p->status_mutex);
  return p->status;	
}

int swfdec_nmp_open(void *mp, int *p_exit)
{
  MediaPlayer *p = (MediaPlayer *)mp;
  NMP_RESULT ret = NMP_OK;
  NMPPlayer *player = NULL;
  NMPGnashChannel *ch = NULL;
  NMP_MEDIA_TYPE type = MEDIA_TYPE_FLV;
  NMP_MEDIA_PARAM param;
  int r = 0;
  unsigned long sz = 0;   

  if (p == NULL)
	return -1;
  player = new NMPPlayer;
  ch = new NMPGnashChannel;
  if (player == NULL || ch == NULL)
  	goto bailout;
  p->status = MP_CLOSED;
  p->failflag = 0;
  p->mtag = NULL;
  p->bitrate = 320000.0;
  p->player = player;
  p->ch = ch;
  p->x = -1;
  p->y = -1;
  p->w = -1;
  p->h = -1;
  /* open */
  ret = ch->open( NULL,
                io_eof,
                io_error,
                io_dof,
                io_first_peek,
                io_read_callback,
                io_seek_callback,
                io_size_callback,
                io_readysize_callback,
                io_cancel);
  if (ret != NMP_OK)
  {
    printf("error io\n");
    goto bailout;
  }
  r = ipcc_readysize(&sz);
  printf("ipcc_readysize = %ld\n", sz);
  while (sz < 1024 * 128 && !*p_exit
			&& !ipcc_getdof() && !ipcc_getcancel())
  {
    usleep(100000);
    r = ipcc_readysize(&sz);
  	printf("ipcc_readysize = %ld\n", sz);
  }
  if (*p_exit)
  	goto bailout;
  p->status = MP_OPENNING;
  ret = player->open(type, param, (NMPIOChannel *)ch, true);  
  if (ret != NMP_OK)
  {
  	p->failflag = 1;
  	delete ch;
  	p->ch = NULL;
  	return -1;
  }  
  ret = player->setNotifyCallback(notifyCb, mp);
  ret = player->getPMetaTag(p->mtag);
  NMP_MEDIA_INFO info;
  ret = player->getMinfo(info);
  if (ret == NMP_OK && info.stream.bitrate > 0)
  {
	p->bitrate = info.stream.bitrate;
  }
  p->status = MP_OPENED;
  if (*p_exit)
  	goto bailout;
  return 0;
bailout:
  if (player != NULL)
  {
  	ret = player->close(true);
  	delete player;  	
  }
  if (ch != NULL)
  {
    ret = ch->close();
    delete ch;
  }
  p->player = NULL;
  p->ch = NULL;
  p->status = MP_CLOSED;
  return -1;
}

int swfdec_nmp_play(void *mp, int x, int y, int w, int h, int buffer_time, int *p_exit)
{
  NMP_RESULT ret = NMP_OK;
  MediaPlayer *p = (MediaPlayer *)mp;
  NMPPlayer *player = NULL;
  NMPGnashChannel *ch = NULL;
  int r = 0;
  unsigned long sz = 0;   

  if (p == NULL)
	return -1;  
  player = p->player;
  ch = p->ch;
  if (player == NULL || ch == NULL)
  	return -1;
  if (p->status != MP_OPENED)
  	return -1;
  if (buffer_time < 2000)
	buffer_time = 2000;
  r = ipcc_readysize(&sz);
  printf("ipcc_readysize = %ld\n", sz);
  while ((double)sz < p->bitrate * buffer_time / 8000.0 && !*p_exit
			&& !ipcc_getdof() && !ipcc_getcancel())
  {
    usleep(100000);
    r = ipcc_readysize(&sz);
  	printf("ipcc_readysize = %ld\n", sz);
  }
  if (*p_exit)
  	return -1;
  /* play */
  p->status = MP_STARTING;
  {
  	if (x + w > 960)
		w = 960 - x;
	if (y + h > 540)
		h = 540 - y;  	
	Locker locker(&p->size_mutex);
  	if (p->x != -1
		&& (p->x != x || p->y != y 
		|| p->w != w || p->h != h))
  	{
	  x = p->x;
	  y = p->y;
	  w = p->w;
	  h = p->h;
    }
  }
  ret = player->play((NMP_RECT){x, y, w, h}, true);
  {
	Locker locker(&p->status_mutex);
	if (p->status < MP_STARTED)
  	  p->status = MP_STARTED;
  }
  {
	Locker locker(&p->size_mutex);
  	if (p->x != -1
		&& (p->x != x || p->y != y 
		|| p->w != w || p->h != h))
  	{
	  ret = player->move((NMP_RECT){p->x, p->y, p->w, p->h});
    }
  }
  /* end */
  return 0;	
}

int swfdec_nmp_close(void *mp)
{
  NMP_RESULT ret = NMP_OK;
  MediaPlayer *p = (MediaPlayer *)mp;
  NMPPlayer *player = NULL;
  NMPGnashChannel *ch = NULL;

  if (p == NULL)
	return -1;  
  
  player = p->player;
  ch = p->ch;
  if (player != NULL)
  {
  	ret = player->close(true);
  }
  if (ch != NULL)
  {
    ret = ch->close();
    delete ch;
  }
  if (player != NULL)
  {
  	delete player;
  }
  p->player = NULL;
  p->ch = NULL;
  p->x = -1;
  p->y = -1;
  p->w = -1;
  p->h = -1;
  p->status = MP_CLOSED;
  return 0;
}

int swfdec_nmp_pause(void *mp, int on_off)
{
  NMP_RESULT ret = NMP_OK;
  MediaPlayer *p = (MediaPlayer *)mp;
  printf("swfdec_nmp_pause.\n");
  if (p == NULL)	
	return -1;
  NMPPlayer *player = p->player;;
  if (player == NULL || swfdec_nmp_get_status(mp) < MP_STARTED)
  {
	printf("NMPPlayer not play yet.\n");
	return -1;
  }
  ret = player->pause(on_off);
  if (ret != NMP_OK)
  {
	printf("NMPPlayer pause fail.\n");
	return -1;
  }
  return 0;
}

int swfdec_nmp_resize(void *mp, int x, int y, int w, int h)
{
  NMP_RESULT ret = NMP_OK;
  MediaPlayer *p = (MediaPlayer *)mp;
  printf("swfdec_nmp_resize.\n");	
  if (p == NULL)	
	return -1;
  NMPPlayer *player = p->player;;
  MPSTATUS status = swfdec_nmp_get_status(mp);
  if (player == NULL || status < MP_OPENED)
  {
	printf("NMPPlayer not play yet.\n");
	return -1;
  }
  {
  	if (x + w > 960)
		w = 960 - x;
	if (y + h > 540)
		h = 540 - y;
	Locker locker(&p->size_mutex);
	p->x = x;
	p->y = y;
	p->w = w;
	p->h = h;
  	if (status < MP_STARTED)
	  return 0;
    ret = player->move((NMP_RECT){x, y, w, h});
  }
  if (ret != NMP_OK)
  {
	printf("NMPPlayer resize fail.\n");
	return -1;
  }
  return 0;
}

long swfdec_nmp_get_pts(void *mp)
{
  NMP_RESULT ret = NMP_OK;
  MediaPlayer *p = (MediaPlayer *)mp;
  printf("swfdec_nmp_get_pts.\n");		
  if (p == NULL)	
	return 0;
  NMPPlayer *player = p->player;;
  if (player == NULL || swfdec_nmp_get_status(mp) < MP_STARTED)
  {
	printf("NMPPlayer not play yet.\n");
	return 0;
  }
  NMP_TIME mpts = 0;
  ret = player->getPts(mpts);
  if (ret == NMP_OK)
  {
	return (long)mpts;
  }
  return 0;	
}

double swfdec_nmp_get_bitrate(void *mp)
{
  MediaPlayer *p = (MediaPlayer *)mp;

  if (p == NULL || p->player == NULL)
	return 320000.0;
  return p->bitrate;		
}

int swfdec_nmp_get_metadata(void *mp, char **buf, int *size)
{
  MediaPlayer *p = (MediaPlayer *)mp;

  if (p == NULL || p->player == NULL)
	return -1;
  if (p->mtag != NULL)
  {
	*buf = (char *)p->mtag->buf;
	*size = p->mtag->size;
  }
  return 0;
}

int swfdec_nmp_is_fail(void *mp)
{
  MediaPlayer *p = (MediaPlayer *)mp;

  if (p == NULL || p->player == NULL)
	return 0;
  return p->failflag;
}

/*------------------------------------------------------------------------------------------------------------------------*/

typedef enum {
    AMF_DATA_TYPE_NUMBER      = 0x00,
    AMF_DATA_TYPE_BOOL        = 0x01,
    AMF_DATA_TYPE_STRING      = 0x02,
    AMF_DATA_TYPE_MIXEDARRAY  = 0x08,
} AMFDataType;

static double getdouble(unsigned char *src)
{
        unsigned short tmp = 0x0102;
        char p[2];
        double val;
        memcpy(p, &tmp, 2);
        if (p[0] == 0x01) {
                memcpy(&val, src, 8);
        } else {
                int i;
                unsigned char p[8];
                for (i = 0; i< 8; i++) {
                        p[i] = src[8 - i -1];
                }
                memcpy(&val, p, 8);
        }

        return val;
}

static int cpdouble(unsigned char *dst, unsigned char *src, int size)
{
        unsigned short tmp = 0x0102;
        char p[2];
        memcpy(p, &tmp, 2);
        if (p[0] == 0x01) {
                memcpy(dst, src, size);
        } else {
                int i;
                for (i = 0; i< size; i++) {
                        dst[i] = src[size - i -1];
                }
        }
        return 0;
}

static int ScriptObj_String(unsigned char **pbuf, unsigned int *psize, unsigned char *buf, 
        int bufsize)
{
        unsigned int len;

        len = ((*pbuf)[0] << 8) | ((*pbuf)[1]);

        *pbuf += 2;
        *psize -= 2;
        if (bufsize != 0 && len > bufsize) {
                printf("bufsize not enough(%d) (%d)\n", len ,bufsize);
                return -1;
        }
        if (*psize < len)  {
                printf("buf  not enough(%d) (%d)\n", len ,bufsize);
                return -1;
        }
        if (buf != NULL) {
                memcpy(buf, *pbuf, len);
                buf[len] = '\0';
        }
        *psize -= len;
        *pbuf += len;


        return 0;
}


int metadata_getset(unsigned char *buf, unsigned int size, unsigned long long curpts, unsigned long long * startpts, int getset)
{
        unsigned char tag = 0;
//      int ret = 0;
        unsigned char str[12];
        unsigned char *p = buf;

//      printf("%s:%d called.\n", __func__, __LINE__);

        if (size < 13+1+4) {
                printf("size(%u) <= 13+1+4\n",size);
                return -1;
        }

        p += 3;

        if (strncasecmp((char *)p, "onMetaData", 10) != 0) {
                printf("metadata_reset: err: tag(%d)str(%s)!=onMetaData\n", tag, str);
                return -1;
        }
        p += 10;
        size -= 13;

//      printf("%s:%d called.\n", __func__, __LINE__);
        {
                int amf_type;
                double duration = 0, totalduration = 0, tmpdouble = 0;
                unsigned char *p_start = NULL, *p_duration = NULL;
                int count, i;
                unsigned char strname[128];
                if (size < 1) {
                        printf("Error: size(%d)< 1\n", size);
                        return -1;
                }
                amf_type = p[0];
                p++;
                size--;
                if (amf_type != AMF_DATA_TYPE_MIXEDARRAY) {
                        printf("Error: first != AMF_DATA_TYPE_MIXEDARRAY (%d)\n", amf_type);
                        return -1;
                }
                if (size < 4) {
                        printf("Error: size(%d) < 4\n", size);
                        return -1;
                }
                count = ((p[0] << 24) | (p[1] << 16) | (p[2] << 8) | p[3]);

                p += 4;
                size -= 4;

                for (i = 0; i < count; i++) {
                        if (ScriptObj_String(&p, &size, strname, 128) != 0)
                                return -1;
                        if (size < 1) {
                                printf("Error: size(%d) < 4\n", size);
                                return -1;
                        }
                        amf_type = p[0];
                        p++;
                        size--; 

                        switch (amf_type) {
                                case AMF_DATA_TYPE_NUMBER:
                                        if (size < 8) {
                                                printf("Error: AMF_DATA_TYPE_NUMBER size(%d)< 8\n", size);
                                                return -1;
                                        }
                                        if (!strcmp((char *)strname,"duration")) {
                                                p_duration = p;
                                                duration = getdouble(p);
                                        } else if (!strcmp((char *)strname,"starttime")) {
                                                p_start = p; 
                                                if (getset == 0)
                                                {
                                                  *startpts = (unsigned long long)(getdouble(p) * 1000.0);
                                                  return 0;
                                                }
                                        } else if (!strcmp((char *)strname,"totalduration")) {
                                                totalduration = getdouble(p);
                                        }

                                        p += 8;
                                        size -= 8;
                                        break;
                                case AMF_DATA_TYPE_BOOL :
                                        if (size < 8) {
                                                printf("Error: AMF_DATA_TYPE_BOOL size(%d)< 1", size);
                                                return -1;
                                        }
                                         p++;
                                         size--;
                                         break;

                                case AMF_DATA_TYPE_STRING:
                                        if (ScriptObj_String(&p, &size, NULL, 0) != 0)
                                                return -1;
                                        break;
                                default:
                                        printf("error or unparse type in sec (%d)\n", amf_type);
                                        return -1; 
                           
                        }
                }

//              printf("%s:%d called.\n", __func__, __LINE__);

                if (p_start != NULL) {
                        tmpdouble = curpts/1000.0;
                        cpdouble(p_start, (unsigned char *)&tmpdouble, 8);
                        printf("new curpts %f\n", tmpdouble);
                }
                if (p_duration != NULL) {
                        if (totalduration < 1.0)
                                tmpdouble = duration - curpts/1000.0;
                        else
                                tmpdouble = totalduration - curpts/1000.0;
                        cpdouble(p_duration,(unsigned char *)&tmpdouble, 8);
                        printf("new duration %f totalduration (%f)\n", tmpdouble, totalduration);
                }
        }
//      printf("%s:%d called.\n", __func__, __LINE__);
        return 0;
}

/*------------------------------------------------------------------------------------------------------------------------*/

int swfdec_nmp_get_starttime(void *mp, char *buf, int size, unsigned int *start_time)
{
  unsigned long long s_time = 0;
  if (buf == NULL || size == 0)
	return -1;
  if (metadata_getset((unsigned char *)buf, size, 0, &s_time, 0) == 0)
  	*start_time = s_time;
  printf("starttime = %u\n", *start_time);
  return 0;
}

int swfdec_nmp_set_starttime(void *mp, char **buf, int *size, unsigned int start_time)
{
  unsigned long long s_time = start_time;
  MediaPlayer *p = (MediaPlayer *)mp;

  if (buf == NULL || size == NULL)
	return -1;
  if (p == NULL || p->player == NULL)
    return -1;
  if (p->mtag == NULL || p->mtag->buf == NULL)
	return -1;
  *size = p->mtag->size;
  *buf = (char *)malloc(*size);
  if (*buf == NULL)
	return -1;
  memcpy(*buf, (char *)p->mtag->buf, *size);
  if (metadata_getset((unsigned char *)*buf, *size, s_time, NULL, 1) == 0)
  	return 0;
  free(buf);
  return -1;
}

