use std::ffi::c_int;
use std::io::Write;
use std::ptr::{null_mut};
use std::sync::{Condvar};
use std::thread::ThreadId;
use godot::engine::{Image, ImageTexture, Texture};
use godot::engine::image::Format;
use godot::prelude::{GdRef, real, Rect2};
use libc::EAGAIN;
use rsmpeg::avcodec::{AVCodecContext, AVPacket};
use rsmpeg::avformat::{AVInputFormat, AVStream};
use rsmpeg::avutil::{av_cmp_q, av_make_q, av_mul_q, av_rescale_q, AVFrame, AVSampleFormat, AVPixelFormat, AVImage};
use rsmpeg::error::RsmpegError;
use rsmpeg::error::RsmpegError::AVError;
use rsmpeg::swresample::SwrContext;
use rsmpeg::ffi::{AV_NOPTS_VALUE, av_image_alloc, sws_scale, av_get_pix_fmt_name, AVChannelLayout, avcodec_flush_buffers, av_image_get_buffer_size, AVERROR_EOF, AVFormatContext, AVRational, av_rescale, AVSubtitleRect, sws_getCachedContext, AV_LOG_FATAL, av_log};
use rsmpeg::ffi::{AVPixelFormat_AV_PIX_FMT_RGB8, AVPixelFormat_AV_PIX_FMT_RGBA, AVPixelFormat_AV_PIX_FMT_RGB24};
use rsmpeg::swscale::SwsContext;
use crate::playback::decoder::Decoder;
use crate::playback::frame::{Frame, FrameQueue, MutexFrameQueue};
use crate::playback::packet::{MutexPacketQueue, PacketQueue};


#[macro_export]
macro_rules! ffmax {
    ( $a:tt , $b:tt ) => {if $a > $b { $a }  else { $b }};
}

mod constant;
mod decoder;
mod frame;
mod mutex;
mod packet;

struct AudioParams {
    freq : i64,
    ch_layout : AVChannelLayout,
    fmt : AVSampleFormat,
    frame_size: i64,
    bytes_per_sec: i64,
}

struct Clock {
    pts : f32,           /* clock base */
    pts_drift: f32,     /* clock base minus time at which we updated the clock */
    last_updated: f32,
    speed: f32,
    serial : i64 ,          /* clock is based on a packet with this serial */
    paused : bool,
    queue_serial: *const i64,    /* pointer to the current packet queue serial, used for obsolete clock detection */
}


enum AvSyncMode {
    AV_SYNC_AUDIO_MASTER, /* default choice */
    AV_SYNC_VIDEO_MASTER,
    AV_SYNC_EXTERNAL_CLOCK, /* synchronize to an external clock */
}



struct VideoState {
    read_tid: Box<ThreadId>,
    iformat: Box<AVInputFormat>,
    abort_request : bool,
    force_refresh : bool,
    paused: bool,
    last_paused : i64,
    queue_attachments_req: i64,
    seek_req : i64,
    seek_flags : i64,
    seek_pos: i64,
    seek_rel : i64,
    read_pause_return : i64,
    ic : Box<AVFormatContext>,
    realtime: i64,

    audclk : Clock,
    vidclk : Clock,
    extclk : Clock,

    pictq : MutexFrameQueue,
    subpq : MutexFrameQueue,
    sampq : MutexFrameQueue,

    auddec : Decoder,
    viddec : Decoder,
    subdec : Decoder,

    audio_stream: i64,

    av_sync_type: i64,

    audio_clock: f32,
    audio_clock_serial : i64,
    audio_diff_cum : f32, /* used for AV difference average computation */
    audio_diff_avg_coef : f32,
    audio_diff_threshold: f32,
    audio_diff_avg_count: i64,
    audio_st: *const AVStream,
    audioq : PacketQueue,
    audio_hw_buf_size: i64,
    audio_buf: *const i8,
    audio_buf1 : *const i8,
    audio_buf_size : usize, /* in bytes */
    audio_buf1_size : usize,
    audio_buf_index: i64, /* in bytes */
    audio_write_buf_size : i64,
    audio_volume : i64,
    muted : bool,
    audio_src : AudioParams,
    audio_filter_src:  AudioParams,
    audio_tgt : AudioParams,
    swr_ctx: *const SwrContext,
    frame_drops_early : bool,
    frame_drops_late : bool,

    subtitle_stream: usize,
    subtitle_st :Option<Box<AVStream>>,
    subtitleq: PacketQueue,

    width: usize,
    height: usize,
    xleft: usize,
    ytop: usize,

    img_convert_ctx: Box<SwsContext>,
}

enum EShowMode {
    SHOW_MODE_NONE = -1, SHOW_MODE_VIDEO = 0, SHOW_MODE_WAVES, SHOW_MODE_RDFT, SHOW_MODE_NB}

// static sample_array: [i16 ; SAMPLE_ARRAY_SIZE as usize] = [];
// int sample_array_index;
// int last_i_start;
// RDFTContext *rdft;
// int rdft_bits;
// FFTSample *rdft_data;
// int xpos;
// double last_vis_time;
// SDL_Texture *vis_texture;
// SDL_Texture *sub_texture;
// SDL_Texture *vid_texture;
//
// int subtitle_stream;
// AVStream *subtitle_st;
// PacketQueue subtitleq;
//
// double frame_timer;
// double frame_last_returned_time;
// double frame_last_filter_delay;
// int video_stream;
// AVStream *video_st;
// PacketQueue videoq;
// double max_frame_duration;      // maximum duration of a frame - above this, we consider the jump a timestamp discontinuity
// struct SwsContext *sub_convert_ctx;
// int eof;
//
// char *filename;
// int width, height, xleft, ytop;
// int step;
//
// int vfilter_idx;
// AVFilterContext *in_video_filter;   // the first filter in the video chain
// AVFilterContext *out_video_filter;  // the last filter in the video chain
// AVFilterContext *in_audio_filter;   // the first filter in the audio chain
// AVFilterContext *out_audio_filter;  // the last filter in the audio chain
// AVFilterGraph *agraph;              // audio filter graph
//
// int last_video_stream, last_audio_stream, last_subtitle_stream;
//
// SDL_cond *continue_read_thread;
// } VideoState;
//
// /* options specified by the user */
// static const AVInputFormat *file_iformat;
// static const char *input_filename;
// static const char *window_title;
// static int default_width  = 640;
// static int default_height = 480;
// static int screen_width  = 0;
// static int screen_height = 0;
// static int screen_left = SDL_WINDOWPOS_CENTERED;
// static int screen_top = SDL_WINDOWPOS_CENTERED;
// static int audio_disable;
// static int video_disable;
// static int subtitle_disable;
// static const char* wanted_stream_spec[AVMEDIA_TYPE_NB] = {0};
// static int seek_by_bytes = -1;
// static float seek_interval = 10;
// static int display_disable;
// static int borderless;
// static int alwaysontop;
// static int startup_volume = 100;
// static int show_status = -1;
// static int av_sync_type = AV_SYNC_AUDIO_MASTER;
// static int64_t start_time = AV_NOPTS_VALUE;
// static int64_t duration = AV_NOPTS_VALUE;
// static int fast = 0;
// static int genpts = 0;
// static int lowres = 0;
static DECODER_REORDER_PTS: bool = false;
// static int autoexit;
// static int exit_on_keydown;
// static int exit_on_mousedown;
// static int loop = 1;
// static int framedrop = -1;
// static int infinite_buffer = -1;
// static enum ShowMode show_mode = SHOW_MODE_NONE;
// static const char *audio_codec_name;
// static const char *subtitle_codec_name;
// static const char *video_codec_name;
// double rdftspeed = 0.02;
// static int64_t cursor_last_shown;
// static int cursor_hidden = 0;
// static const char **vfilters_list = NULL;
// static int nb_vfilters = 0;
// static char *afilters = NULL;
// static int autorotate = 1;
// static int find_stream_info = 1;
// static int filter_nbthreads = 0;








fn calculate_display_rect(
    rect: &mut Rect2,
    scr_xleft: i64,
    scr_ytop: i64,
    scr_width: i64,
    scr_height: i64,
    pic_width: i64,
    pic_height: i64,
    pic_sar: AVRational,
) {
    let mut aspect_ratio = pic_sar;
    let (mut width, mut height, mut x, mut y): (i64, i64, i64, i64);

    if av_cmp_q(aspect_ratio, av_make_q(0, 1)) <= 0 {
        aspect_ratio = av_make_q(1, 1);
    }

    aspect_ratio = av_mul_q(aspect_ratio, av_make_q(pic_width as c_int, pic_height as c_int));

    // XXX: we suppose the screen has a 1.0 pixel ratio
    height = scr_height;
    width = unsafe { av_rescale(height, aspect_ratio.num.into(), aspect_ratio.den.into()) & !1 };
    if width > scr_width {
        width = scr_width;
        height = unsafe { av_rescale(width, aspect_ratio.den.into(), aspect_ratio.num.into()) & !1  };
    }
    x = (scr_width - width) / 2;
    y = (scr_height - height) / 2;
    rect.position.x = (scr_xleft + x) as real;
    rect.position.y = (scr_ytop + y) as real;
    rect.size.x = ffmax!((width as f32), 1.0);
    rect.size.y = ffmax!((height as f32), 1.0);
}



struct VideoStreamPlayback {
    video_state : VideoState,

    vid_texture : Box<ImageTexture>,
    image_buffer : Vec<u8>,
    transfer_frame: Option<Box<AVFrame>>,
    sws_flag: usize,

}

impl VideoStreamPlayback {
    fn video_image_display(&mut self) {
        let mut vp = self.video_state.pictq.peek_last().as_mut().unwrap(); //Fixme
        let mut sp = self.video_state.subpq.peek();
        let mut rect: Rect2 = Rect2::default(); //Fixme

        // if self.subtitle_st.is_some() {
        //         if let Some(sp) = sp {
        //             if vp.pts >= (*sp).pts + ((*sp).sub.unwrap().start_display_time as f32 / 1000.0) { // FIXME
        //                 if !(*sp).uploaded  {
        //                     let mut pixels: [*mut u8; 4] = [std::ptr::null_mut(); 4];
        //                     let mut pitch: [i32; 4] = [0; 4];
        //                     let mut i: i32;
        //                     if sp.width == 0 || sp.height == 0 {
        //                         sp.width = vp.width;
        //                         sp.height = vp.height;
        //                     }
        //
        //                     // if (realloc_texture(&vstate->sub_texture, SDL_PIXELFORMAT_ARGB8888, sp->width, sp->height, SDL_BLENDMODE_BLEND, 1) < 0)
        //                     //     return;
        //
        //                     for i in 0..sp.sub.unwrap().num_rects { // FIXME
        //                         let sub_rect: *mut AVSubtitleRect = sp.sub.unwrap().rects[i as usize]; // FIXME
        //
        //                         unsafe {
        //
        //                         (*sub_rect).x = av_clip((*sub_rect).x, 0, sp.width);
        //                         (*sub_rect).y = av_clip((*sub_rect).y, 0, sp.height);
        //                         (*sub_rect).w = av_clip((*sub_rect).w, 0, sp.width - (*sub_rect).x);
        //                         (*sub_rect).h = av_clip((*sub_rect).h, 0, sp.height - (*sub_rect).y);
        //
        //                         self.sub_convert_ctx = sws_getCachedContext(
        //                             self.sub_convert_ctx,
        //                             (*sub_rect).w,
        //                             (*sub_rect).h,
        //                             AVPixelFormat::AV_PIX_FMT_PAL8,
        //                             (*sub_rect).w,
        //                             (*sub_rect).h,
        //                             AVPixelFormat::AV_PIX_FMT_BGRA,
        //                             0,
        //                             null_mut(),
        //                             null_mut(),
        //                             null_mut(),
        //                         );
        //                         if self.vstate.sub_convert_ctx.is_null() {
        //                             av_log(
        //                                 null_mut(),
        //                                 AV_LOG_FATAL,
        //                                 "Cannot initialize the conversion context\n\0".as_ptr()
        //                             );
        //                             return;
        //                         }
        //                         }
        //                         println!("sws_scale!");
        //                         // if !SDL_LockTexture(vstate->sub_texture, (SDL_Rect *)sub_rect, (void **)pixels, pitch) {
        //                         //     sws_scale(vstate->sub_convert_ctx, (const uint8_t * const *)sub_rect->data, sub_rect->linesize,
        //                         //               0, sub_rect->h, pixels, pitch);
        //                         //     SDL_UnlockTexture(vstate->sub_texture);
        //                         // }
        //                     }
        //                     (*sp).uploaded = 1;
        //                 }
        //             } else {
        //                 sp = &None;
        //             }
        //         }
        // }

        calculate_display_rect(&mut rect, self.video_state.xleft as i64, self.video_state.ytop as i64, self.video_state.width as i64, self.video_state.height as i64, vp.width as i64, vp.height as i64, vp.sar);
        if !vp.uploaded {
            if self.upload_texture(&vp.frame, &self.video_state.img_convert_ctx).is_err() {
                return;
            }
            vp.uploaded = true;
            vp.flip_v = vp.frame.linesize[0] < 0;
        }

        // if (sp) {
        //     int i;
        //     double xratio = (double) rect.size.x / (double) sp->width;
        //     double yratio = (double) rect.size.y / (double) sp->height;
        //     for (i = 0; i < sp->sub.num_rects; i++) {
        //     }
        // }

    }

    fn upload_texture(&mut self, frame: &AVFrame, img_convert_ctx: &SwsContext ) -> Result<(), RsmpegError> {
        let mut ret: c_int = 0;
        let mut godot_pix_fmt: c_int = 0;
        // get_godot_pix_fmt((*frame).format, &mut godot_pix_fmt);
        let godot_pix_fmt = Self::convert_px_format_godot(frame.format);

        let mut init_texture: bool = false;
        let use_conversion: bool = godot_pix_fmt == Format::FORMAT_MAX;

        //if !self.vid_texture.as_ref().is_valid() { // FIXME ???
        //    self.vid_texture.as_ref().instance();
        //}

        if self.vid_texture.get_width() != (*frame).width.into() || self.vid_texture.get_height() != (*frame).height.into() {
            init_texture = true;
        }

        if init_texture {
            // self.vid_texture.create((*frame).width, (*frame).height, Image::FORMAT_RGBA8, Texture::FLAG_FILTER | Texture::FLAG_VIDEO_SURFACE); // FIXME
            println!("ffplay::upload_texture, texture allocated: {}x{}", (*frame).width, (*frame).height);

            let image_buffer_size: c_int = av_image_get_buffer_size(AVPixelFormat_AV_PIX_FMT_RGBA, (*frame).width, (*frame).height, 1);
            self.image_buffer.resize(image_buffer_size as usize, 0);
            if self.transfer_frame.is_some() {
                self.transfer_frame = None
                //av_freep(&mut self.transfer_frame.data[0]);
                //av_frame_free(&mut transfer_frame);
            }

            if use_conversion {
                self.transfer_frame = Some(Box::from(AVFrame::new()));
                self.transfer_frame.unwrap().format = AVPixelFormat_AV_PIX_FMT_RGBA;
                self.transfer_frame.unwrap().width = frame.width;
                self.transfer_frame.unwrap().height = frame.height;

                let image = AVImage::new(
                    self.transfer_frame.unwrap().format,
                    self.transfer_frame.unwrap().width,
                    self.transfer_frame.unwrap().height,
                    32 );

                // ret = av_image_alloc(
                //     self.transfer_frame.unwrap().data,
                //     self.transfer_frame.unwrap().linesize,
                //     self.transfer_frame.unwrap().width,
                //     self.transfer_frame.unwrap().height,
                //     self.transfer_frame.unwrap().format,
                //     32,
                // );
                if image.is_none(){
                    eprintln!("Could not allocate raw picture buffer");
                }

                // println!(
                //     "converting pixel format from {} to {}",
                //     av_get_pix_fmt_name(AVPixelFormat((*frame).format)),
                //     av_get_pix_fmt_name(AVPixelFormat_AV_PIX_FMT_RGBA)
                // );
            }
            // else {
            //     println!(
            //         "using direct pixel format: {}",
            //         av_get_pix_fmt_name(AVPixelFormat((*frame).format))
            //     );
            // }
        }

        if !use_conversion {
            let mut img = Image::new();
            img.set_data((*frame).width.into(), (*frame).height.into(), false, Format::FORMAT_RGBA8, self.image_buffer.as_slice().into());
            *self.vid_texture= *ImageTexture::create_from_image(img).unwrap();
        }
        // else {
        //     // This should only happen if we are not using avfilter...
        //     *img_convert_ctx = sws_getCachedContext(
        //         *img_convert_ctx,
        //         (*frame).width,
        //         (*frame).height,
        //         AVPixelFormat((*frame).format),
        //         (*frame).width,
        //         (*frame).height,
        //         AVPixelFormat::AV_PIX_FMT_RGBA,
        //         self.sws_flags,
        //         null_mut(),
        //         null_mut(),
        //         null_mut(),
        //     );
        //
        //     if !(*img_convert_ctx).is_null() {
        //         sws_scale(
        //             *img_convert_ctx,
        //             (*frame).data,
        //             (*frame).linesize,
        //             0,
        //             (*frame).height,
        //             self.transfer_frame.data,
        //             self.transfer_frame.linesize,
        //         );
        //
        //         let pixels: *mut u8 = self.image_buffer.write().ptr();
        //         let fdata: *mut *mut u8 = self.transfer_frame.data;
        //         let lines: c_int = self.transfer_frame.linesize[0];
        //         for y in 0..self.transfer_frame.height {
        //             let src_slice: &[u8] = unsafe {
        //                 slice::from_raw_parts((*(fdata.offset(0))).offset((y * lines) as isize), self.transfer_frame.width as usize * 4)
        //             };
        //             let dest_slice: &mut [u8] = unsafe {
        //                 slice::from_raw_parts_mut(pixels.offset((y * self.transfer_frame.width * 4) as isize), self.transfer_frame.width as usize * 4)
        //             };
        //             dest_slice.copy_from_slice(src_slice);
        //         }
        //         let img = Image::new((*frame).width, (*frame).height, 0, Image::FORMAT_RGBA8, self.image_buffer);
        //         self.vid_texture.set_data(img);
        //     } else {
        //         av_log(
        //             ptr::null_mut(),
        //             AV_LOG_FATAL,
        //             "Cannot initialize the conversion context\n\0".as_ptr() as *const i8,
        //         );
        //         ret = -1;
        //     }
        // }

        Ok(())
    }

    fn convert_px_format_godot(frmt: AVPixelFormat ) -> Format {
        match frmt {
            AVPixelFormat_AV_PIX_FMT_RGB8 => Format::FORMAT_RGB8,
            AVPixelFormat_AV_PIX_FMT_RGBA => Format::FORMAT_RGBA8,
            AVPixelFormat_AV_PIX_FMT_RGB24 => Format::FORMAT_RGB8,
            AVPixelFormat_AV_PIX_FMT_RGB0 => Format::FORMAT_RGB8,
            _ => Format::FORMAT_MAX
        }
    }

}

