#include "aiplay.h"

#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <pthread.h>

#include <GLES2/gl2.h>
#include <EGL/egl.h>
#include <sys/time.h>

#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include <libswscale/swscale.h>

#include "egl_ctx.h"
#include "opengles_ctx.h"

#define log_trace printf
#define log_debug printf
#define log_info printf
#define log_warn printf
#define log_error printf
#define log_fatal printf

static inline int64_t sr_time_begin()
{
  struct timeval tv = {0};
  if (gettimeofday(&tv, NULL) != 0)
  {
    assert(0);
  }
  return (1000000LL * tv.tv_sec + tv.tv_usec);
}

static inline int64_t sr_time_passed(int64_t begin_microsecond)
{
  struct timeval tv = {0};
  if (gettimeofday(&tv, NULL) != 0)
  {
    assert(0);
  }
  return ((1000000LL * tv.tv_sec + tv.tv_usec) - begin_microsecond);
}

#define QUEUE_SIZE 10

// 定义消息结构
typedef struct
{
  int id;
  char message[256];
} Message;

// 消息队列结构
typedef struct
{
  Message messages[QUEUE_SIZE];
  int front;
  int rear;
  int size;
  pthread_mutex_t mutex;
  pthread_cond_t not_empty;
  pthread_cond_t not_full;
} MessageQueue;

// 初始化消息队列
void initMessageQueue(MessageQueue *queue)
{
  queue->front = 0;
  queue->rear = 0;
  queue->size = 0;
  pthread_mutex_init(&queue->mutex, NULL);
  pthread_cond_init(&queue->not_empty, NULL);
  pthread_cond_init(&queue->not_full, NULL);
}

void releaseMessageQueue(MessageQueue *queue)
{
  pthread_mutex_destroy(&queue->mutex);
  pthread_cond_destroy(&queue->not_empty);
  pthread_cond_destroy(&queue->not_full);
}

// 发送消息到队列
void sendMessage(MessageQueue *queue, Message message)
{
  pthread_mutex_lock(&queue->mutex);

  while (queue->size >= QUEUE_SIZE)
  {
    pthread_cond_wait(&queue->not_full, &queue->mutex);
  }

  queue->messages[queue->rear] = message;
  queue->rear = (queue->rear + 1) % QUEUE_SIZE;
  queue->size++;

  pthread_cond_signal(&queue->not_empty);
  pthread_mutex_unlock(&queue->mutex);
}

// 从队列接收消息
Message receiveMessage(MessageQueue *queue)
{
  pthread_mutex_lock(&queue->mutex);

  while (queue->size <= 0)
  {
    pthread_cond_wait(&queue->not_empty, &queue->mutex);
  }

  Message message = queue->messages[queue->front];
  queue->front = (queue->front + 1) % QUEUE_SIZE;
  queue->size--;

  pthread_cond_signal(&queue->not_full);
  pthread_mutex_unlock(&queue->mutex);

  return message;
}

Message receiveMessageWait(MessageQueue *queue, uint64_t delay)
{
  pthread_mutex_lock(&queue->mutex);

  while (queue->size <= 0)
  {
    if (delay > 0)
    {
      struct timespec ts;
      delay += sr_time_begin();
      ts.tv_sec = delay / 1000000ULL;
      ts.tv_nsec = (delay % 1000000ULL) * 1000ULL;
      if (pthread_cond_timedwait(&queue->not_empty, &queue->mutex, &ts) == ETIMEDOUT)
      {
        pthread_mutex_unlock(&queue->mutex);
        return (Message){-1, {0}};
      }
    }
    else
    {
      pthread_cond_wait(&queue->not_empty, &queue->mutex);
    }
  }

  Message message = queue->messages[queue->front];
  queue->front = (queue->front + 1) % QUEUE_SIZE;
  queue->size--;

  pthread_cond_signal(&queue->not_full);
  pthread_mutex_unlock(&queue->mutex);

  return message;
}

struct aiplay
{
  int running;
  pthread_t tid;
  int width;
  int height;
  void *native_window_handler;
  MessageQueue queue;

  aiplay_event_cb_t cb;
  void *user_ctx;
};

typedef struct media_ctx
{
  int frame_count;
  int video_stream_idx;
  int64_t start_time;
  int64_t play_time;
  int64_t timestamp;
  AVFormatContext *format_ctx;
  AVCodecContext *codec_ctx;
  AVFrame *frame;
} media_ctx_t;

void media_ctx_release(media_ctx_t *ctx)
{
  printf("media_ctx_release enter\n");
  if (ctx->format_ctx)
  {
    AVFormatContext *format = ctx->format_ctx;
    ctx->format_ctx = NULL;
    avformat_close_input(&format);
  }

  if (ctx->codec_ctx)
  {
    AVCodecContext *codec = ctx->codec_ctx;
    ctx->codec_ctx = NULL;
    avcodec_free_context(&codec);
  }

  if (ctx->frame)
  {
    AVFrame *frame = ctx->frame;
    ctx->frame = NULL;
    av_frame_free(&frame);
  }
  printf("media_ctx_release exit\n");
}

static int media_ctx_create(media_ctx_t *ctx, const char *filename)
{
  media_ctx_release(ctx);

  AVFormatContext *format_ctx = NULL;
  if (avformat_open_input(&format_ctx, filename, NULL, NULL) != 0)
  {
    fprintf(stderr, "Failed to open input file\n");
    return 1;
  }

  if (avformat_find_stream_info(format_ctx, NULL) < 0)
  {
    fprintf(stderr, "Failed to find stream information\n");
    return 1;
  }

  // 寻找视频流
  int video_stream_idx = -1;
  for (int i = 0; i < format_ctx->nb_streams; i++)
  {
    if (format_ctx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO)
    {
      video_stream_idx = i;
      break;
    }
  }

  if (video_stream_idx == -1)
  {
    log_error("Video stream not found in input file\n");
    return 1;
  }

  AVCodecParameters *codec_params = format_ctx->streams[video_stream_idx]->codecpar;
  AVCodec *codec = avcodec_find_decoder(codec_params->codec_id);
  printf("Codec name: %s\n", codec->name);

  AVCodecContext *codec_ctx = avcodec_alloc_context3(codec);

  if (avcodec_parameters_to_context(codec_ctx, codec_params) < 0)
  {
    log_error("Failed to copy codec parameters to codec context\n");
    return 1;
  }

  if (avcodec_open2(codec_ctx, codec, NULL) < 0)
  {
    log_error("Failed to open codec\n");
    return 1;
  }

  // 创建视频帧
  AVFrame *frame = av_frame_alloc();

  ctx->start_time = 0;
  ctx->play_time = 0;
  ctx->timestamp = 0;
  ctx->format_ctx = format_ctx;
  ctx->codec_ctx = codec_ctx;
  ctx->frame = frame;
  ctx->video_stream_idx = video_stream_idx;
  ctx->frame_count = 0;

  return 0;
}

void *aiplay_loop(void *arg)
{

  log_debug("aiplay_loop enter\n");

  char filename[256] = {0};
  egl_ctx_t *egl_ctx = NULL;
  opengles_ctx_t *gles_ctx = NULL;
  media_ctx_t media_ctx = {0};
  aiplay_t *ap = (aiplay_t *)arg;
  MessageQueue *queue = &ap->queue;

  egl_ctx_create(&egl_ctx, ap->native_window_handler);

  opengles_ctx_create(&gles_ctx);

  glViewport(0, 0, ap->width, ap->height);
  glClearColor(0.0, 0.0, 0.0, 1.0);

  // 初始化FFmpeg
  //av_register_all();

  // 解码并渲染每一帧
  AVPacket packet;
  av_init_packet(&packet);

  int64_t delay = 0, frame_render_start_time = 0, frame_render_used_time = 0;

  Message message;
  while (ap->running)
  {
    if (media_ctx.format_ctx == NULL)
    {
      message = receiveMessage(queue);
    }
    else
    {
      media_ctx.play_time = sr_time_passed(media_ctx.start_time);
      frame_render_used_time = sr_time_passed(frame_render_start_time);
      delay = media_ctx.timestamp - media_ctx.play_time - frame_render_used_time;
      if (delay <= 0)
      {
	      delay = 1;
      }
      message = receiveMessageWait(queue, delay);
    }

    if (message.id != -1)
    {
      log_debug("MessageQueue received: id %d %s\n", message.id, message.message);
      if (message.id == 1)
      {
        snprintf(filename, 256, "%s", message.message);
        media_ctx_create(&media_ctx, filename);
        media_ctx.start_time = sr_time_begin();
      }
      else if (message.id == 0)
      {
        log_debug("MessageQueue received: release palyer\n");
        media_ctx_release(&media_ctx);
        break;
      }
      message.id = -1;
    }

    if (media_ctx.format_ctx)
    {
      if (av_read_frame(media_ctx.format_ctx, &packet) == 0)
      {

        if (packet.stream_index == media_ctx.video_stream_idx)
        {
          frame_render_start_time = sr_time_begin();

          if (avcodec_send_packet(media_ctx.codec_ctx, &packet) == 0)
          {

            media_ctx.timestamp = (packet.pts * 1000000 * av_q2d(media_ctx.format_ctx->streams[packet.stream_index]->time_base));

            if (avcodec_receive_frame(media_ctx.codec_ctx, media_ctx.frame) == 0)
            {

              if (ap->cb){
                ap->cb(ap->user_ctx, (aiplay_event_t){2, media_ctx.frame_count++});
              }
              glClear(GL_COLOR_BUFFER_BIT);

              opengles_ctx_draw_yuv420p(gles_ctx, media_ctx.frame->width, media_ctx.frame->height,
                                        (const char *)media_ctx.frame->data[0],
                                        (const char *)media_ctx.frame->data[1],
                                        (const char *)media_ctx.frame->data[2],
                                        media_ctx.frame->linesize[0],
                                        media_ctx.frame->linesize[1],
                                        media_ctx.frame->linesize[2]);

              egl_ctx_swap_buffers(egl_ctx);
            }
          }
        }
        av_packet_unref(&packet);
      }
      else
      {
        media_ctx_release(&media_ctx);
        if (ap->cb)
        {
          ap->cb(ap->user_ctx, (aiplay_event_t){1, 0});
        }
      }
    }
  }

  opengles_ctx_release(&gles_ctx);
  egl_ctx_release(&egl_ctx);

  log_debug("aiplay_loop exit\n");

  return NULL;
}

aiplay_t *aiplay_open(void *native_window_handler, int w, int h)
{
  log_debug("aiplay_open enter\n");

  aiplay_t *ap = (aiplay_t *)malloc(sizeof(aiplay_t));
  if (ap == NULL)
  {
    log_error("malloc aiplay failed\n");
    return NULL;
  }

  ap->width = w;
  ap->height = h;
  ap->native_window_handler = native_window_handler;
  initMessageQueue(&ap->queue);

  ap->running = 1;
  if (pthread_create(&ap->tid, NULL, aiplay_loop, ap) != 0)
  {
    log_error("pthread_create failed\n");
    releaseMessageQueue(&ap->queue);
    free(ap);
  }

  log_debug("aiplay_open exit\n");

  return ap;
}

void aiplay_close(aiplay_t *ap)
{
  log_debug("aiplay_close enter\n");
  Message message;
  message.id = 0;
  snprintf(message.message, sizeof(message.message), "Message %d", 0);
  sendMessage(&ap->queue, message);
  pthread_join(ap->tid, NULL);
  releaseMessageQueue(&ap->queue);
  free(ap);
  log_debug("aiplay_close exit\n");
}

void aiplay_play(aiplay_t *ap, const char *filename)
{
  log_debug("aiplay_play enter\n");
  Message message;
  message.id = 1;
  snprintf(message.message, sizeof(message.message), "%s", filename);
  sendMessage(&ap->queue, message);
  log_debug("aiplay_play exit\n");
}

void aiplay_set_callback(aiplay_t *ap, aiplay_event_cb_t cb, void *user_ctx)
{
  ap->cb = cb;
  ap->user_ctx = user_ctx;
}
