extern crate subprocess;
use crate::config_management;
use crate::utils::{get_bundle, is_snap, is_wayland};
use crate::wayland_record::{CursorModeTypes, RecordTypes, WaylandRecorder};
use chrono::prelude::*;
use ffmpeg_sidecar::child::FfmpegChild;
use ffmpeg_sidecar::command::FfmpegCommand;
use gtk::{prelude::*, ResponseType, TextBuffer, TextView};
use gtk::{ButtonsType, DialogFlags, MessageDialog, MessageType};
use gtk::{CheckButton, ComboBoxText, Entry, FileChooserNative, Label, SpinButton, Window};
use std::cell::RefCell;
use std::path::PathBuf;
use std::process::Command;
use std::rc::Rc;
use std::sync::mpsc::Sender;
use std::thread::sleep;
use std::time::Duration;
use subprocess::Exec;
use filename::Filename;

#[derive(Clone)]
pub struct Ffmpeg {
    pub filename: (FileChooserNative, Entry, ComboBoxText),
    pub record_video: CheckButton,
    pub record_audio: CheckButton,
    pub audio_id: ComboBoxText,
    pub record_mouse: CheckButton,
    pub follow_mouse: CheckButton,
    pub record_frames: SpinButton,
    pub record_delay: SpinButton,
    pub command: Entry,
    pub video_process: Option<Rc<RefCell<FfmpegChild>>>,
    pub audio_process: Option<Rc<RefCell<FfmpegChild>>>,
    pub height: Option<u16>,
    pub saved_filename: Option<String>,
    pub unbound: Option<Sender<bool>>,
    pub window: Window,
    pub record_wayland: WaylandRecorder,
    pub record_window: Rc<RefCell<bool>>,
    pub main_context: gtk::glib::MainContext,
    pub temp_video_filename: String,
    pub bundle: String,
    pub video_record_bitrate: SpinButton,
    pub audio_record_bitrate: SpinButton,
    pub error_window: MessageDialog,
    pub error_window_text: Label,
    pub error_details: TextView,
}

impl Ffmpeg {
    pub fn start_record(&mut self, x: u16, y: u16, width: u16, height: u16) -> Option<()> {
        self.saved_filename = Some(
            self.filename
                .0
                .file()
                .unwrap()
                .path()
                .unwrap()
                .join(PathBuf::from(format!(
                    "{}.{}",
                    if self.filename.1.text().to_string().trim().eq("") {
                        Utc::now().to_string().replace(" UTC", "").replace(' ', "-")
                    } else {
                        self.filename.1.text().to_string().trim().to_string()
                    },
                    self.filename.2.active_id().unwrap()
                )))
                .as_path()
                .display()
                .to_string(),
        );

        let is_file_already_exists =
            std::path::Path::new(&self.saved_filename.clone().unwrap()).exists();

        if is_file_already_exists {
            let message_dialog = MessageDialog::new(
                Some(&self.window),
                DialogFlags::all(),
                MessageType::Warning,
                ButtonsType::YesNo,
                &self.bundle,
            );

            let answer = self.main_context.block_on(message_dialog.run_future());
            message_dialog.close();

            if answer != ResponseType::Yes {
                return None;
            }
        }

        if self.record_video.is_active() && !is_wayland() && self.filename.2.active_id().unwrap().as_str() != "gif" {
            let mode = config_management::get("default", "mode");
            let format = "x11grab";
            let display = format!("{}+{},{}",
                                  std::env::var("DISPLAY").unwrap_or_else(|_| ":0".to_string())
                                  .as_str(),
                                  x,
                                  y
            );
            let mut ffmpeg_command = FfmpegCommand::new();

            // record video with specified width and hight
            if self.follow_mouse.is_active() && mode.as_str() == "screen" {
                let width = width as f32 * 0.95;
                let height = height as f32 * 0.95;
                ffmpeg_command.size(width as u32, height as u32);
            } else {
                ffmpeg_command.size(width.into(), height.into());
            }

            // if show mouse switch is enabled, draw the mouse to video
            if self.record_mouse.is_active() {
                ffmpeg_command.args(["-draw_mouse", "1"]);
            } else {
                ffmpeg_command.args(["-draw_mouse", "0"]);
            };

            // if follow mouse switch is enabled, follow the mouse
            if self.follow_mouse.is_active() {
                ffmpeg_command.args(["-follow_mouse", "centered"]);
            }

            // Disable frame rate if value is zero
            if self.record_frames.value() > 0.0 {
                ffmpeg_command.args(["-framerate", &self.record_frames.value().to_string()]);
            }

            // Video format && input
            ffmpeg_command.format(format)
                          .input(display);

            // Disable bitrate if value is zero
            if self.video_record_bitrate.value() > 0.0 {
                ffmpeg_command.args([
                    "-b:v",
                    &format!("{}K", self.video_record_bitrate.value()),
                ]);
            }

            let video_filename = format!(
                "{}.temp.without.audio.{}",
                self.saved_filename.as_ref().unwrap(),
                self.filename.2.active_id().unwrap()
            );

            // Output
            ffmpeg_command.args([
                {
                    if self.record_audio.is_active() {
                        video_filename.as_str()
                    } else {
                        self.saved_filename.as_ref().unwrap()
                    }
                },
            ]);
            ffmpeg_command.overwrite();

            // sleep for delay
            sleep(Duration::from_secs(self.record_delay.value() as u64));

            // start recording and return the process id
            self.video_process = Some(Rc::new(RefCell::new(ffmpeg_command.spawn().unwrap())));
        } else if self.record_video.is_active() && !is_wayland() && self.filename.2.active_id().unwrap().as_str() == "gif" {
            let tempfile = tempfile::Builder::new().suffix(".mp4")
                                                   .tempfile().expect("cannot create temp file")
                                                              .keep().expect("cannot keep temp file");
            self.temp_video_filename = tempfile.0.file_name().expect("cannot get file name")
                                                             .to_str().unwrap().to_string();
            let mode = config_management::get("default", "mode");
            let format = "x11grab";
            let display = format!("{}+{},{}",
                                  std::env::var("DISPLAY").unwrap_or_else(|_| ":0".to_string())
                                  .as_str(),
                                  x,
                                  y
            );
            let mut ffmpeg_command = FfmpegCommand::new();

            // record video with specified width and hight
            if self.follow_mouse.is_active() && mode.as_str() == "screen" {
                let width = width as f32 * 0.95;
                let height = height as f32 * 0.95;
                ffmpeg_command.size(width as u32, height as u32);
            } else {
                ffmpeg_command.size(width.into(), height.into());
            }

            // if show mouse switch is enabled, draw the mouse to video
            if self.record_mouse.is_active() {
                ffmpeg_command.args(["-draw_mouse", "1"]);
            } else {
                ffmpeg_command.args(["-draw_mouse", "0"]);
            };

            // if follow mouse switch is enabled, follow the mouse
            if self.follow_mouse.is_active() {
                ffmpeg_command.args(["-follow_mouse", "centered"]);
            }

            // Disable frame rate if value is zero
            if self.record_frames.value() > 0.0 {
                ffmpeg_command.args(["-framerate", &self.record_frames.value().to_string()]);
            }

            // Video format && input
            ffmpeg_command.format(format)
                          .input(display);

            // Disable bitrate if value is zero
            if self.video_record_bitrate.value() > 0.0 {
                ffmpeg_command.args([
                    "-b:v",
                    &format!("{}K", self.video_record_bitrate.value()),
                ]);
            }

            // Output
            ffmpeg_command.arg(self.temp_video_filename.clone())
                          .overwrite();

            // sleep for delay
            sleep(Duration::from_secs(self.record_delay.value() as u64));

            // start recording and return the process id
            self.video_process = Some(Rc::new(RefCell::new(ffmpeg_command.spawn().unwrap())));
            self.height = Some(height);
        } else if self.record_video.is_active() && is_wayland() {
            sleep(Duration::from_secs(self.record_delay.value() as u64));

            let tempfile = tempfile::NamedTempFile::new().expect("cannot create temp file").keep().expect("cannot keep temp file");
            self.temp_video_filename = tempfile.0.file_name().expect("cannot get file name").to_str().unwrap().to_string();

            let record_window = self.record_window.take();
            self.record_window.replace(record_window);

            if !self.main_context.block_on(self.record_wayland.start(
                self.temp_video_filename.clone(),
                if record_window {
                    RecordTypes::Window
                } else {
                    RecordTypes::Monitor
                },
                {
                    if self.record_mouse.is_active() {
                        CursorModeTypes::Show
                    } else {
                        CursorModeTypes::Hidden
                    }
                },
            )) {
                println!("failed to start recording");
                return None;
            }
        }

        if self.record_audio.is_active() {
            let mut ffmpeg_command = FfmpegCommand::new();
            ffmpeg_command.format("pulse")
                          .input(&self.audio_id.active_id().unwrap())
                          .format("ogg");
            ffmpeg_command.arg(format!(
                "{}.temp.audio",
                self.saved_filename.as_ref().unwrap()
            ));
            ffmpeg_command.overwrite();
            self.audio_process = Some(Rc::new(RefCell::new(ffmpeg_command.spawn().unwrap())));
        }

        Some(())
    }

    pub fn stop_record(&mut self) {
        // kill the process to stop recording
        if self.video_process.is_some() {
            self.video_process
                .clone()
                .unwrap()
                .borrow_mut()
                .quit()
                .unwrap();
        } else if is_wayland() {
            self.main_context.block_on(self.record_wayland.stop());
        }

        if self.audio_process.is_some() {
            self.audio_process
                .clone()
                .unwrap()
                .borrow_mut()
                .quit()
                .unwrap();
        }

        let video_filename = {
            if is_wayland() {
                self.temp_video_filename.clone()
            } else if !is_wayland() && self.filename.2.active_id().unwrap().as_str() == "gif" {
                self.temp_video_filename.clone()
            } else {
                format!(
                    "{}.temp.without.audio.{}",
                    self.saved_filename.as_ref().unwrap(),
                    self.filename.2.active_id().unwrap()
                )
            }
        };

        let audio_filename = format!("{}.temp.audio", self.saved_filename.as_ref().unwrap());

        let is_video_record = {
            std::path::Path::new(video_filename.as_str()).exists()
        };
        let is_audio_record = std::path::Path::new(audio_filename.as_str()).exists();

        if is_video_record {
            if is_wayland() && self.filename.2.active_id().unwrap().as_str() != "gif" {
                // convert webm to specified format
                let mut ffmpeg_command = FfmpegCommand::new();
                ffmpeg_command.input(self.temp_video_filename.as_str());
                if self.video_record_bitrate.value() > 0.0 {
                    ffmpeg_command.args([
                        "-b:v",
                        &format!("{}K", self.video_record_bitrate.value()),
                    ]);
                }
                ffmpeg_command.args([
                    "-c:a",
                    self.filename.2.active_id().unwrap().as_str(),
                    self.saved_filename.as_ref().unwrap(),
                ]).overwrite()
                  .spawn()
                  .unwrap().wait().unwrap();
            } else if is_wayland() && self.filename.2.active_id().unwrap().as_str() == "gif" {
                let fps = 100/self.record_frames.value_as_int();
                let scale = self.height.unwrap();
                let mut ffmpeg_command = FfmpegCommand::new();
                ffmpeg_command.input(self.temp_video_filename.as_str())
                              .filter_complex(
                                  format!("fps={},scale={}:-1:flags=lanczos,[0]split[s0][s1]; [s0]palettegen[p]; [s1][p]paletteuse",
                                  fps,scale));
                if self.video_record_bitrate.value() > 0.0 {
                    ffmpeg_command.args([
                        "-b:v",
                        &format!("{}K", self.video_record_bitrate.value()),
                    ]);
                }
                ffmpeg_command.args(["-loop", "0"])
                              .args([
                                  self.filename.2.active_id().unwrap().as_str(),
                                  self.saved_filename.as_ref().unwrap(),
                              ])
                              .overwrite().spawn().unwrap().wait().expect("failed to convert video to gif");
                if is_audio_record {
                    std::fs::remove_file(audio_filename.clone()).unwrap();
                }
            } else if !is_wayland() && self.filename.2.active_id().unwrap().as_str() == "gif" {
                // convert mp4 to gif
                let fps = 100/self.record_frames.value_as_int();
                let scale = self.height.unwrap();
                let mut ffmpeg_command = FfmpegCommand::new();
                ffmpeg_command.input(format!("file:{}", video_filename.as_str()))
                              .filter_complex(
                                  format!("fps={},scale={}:-1:flags=lanczos,[0]split[s0][s1]; [s0]palettegen[p]; [s1][p]paletteuse",
                                  fps,scale)
                              )
                              .args(["-loop", "0"])
                              .output(self.saved_filename.as_ref().unwrap())
                              .overwrite().spawn().unwrap().wait().expect("failed to convert video to gif");
                if is_audio_record {
                    std::fs::remove_file(audio_filename.clone()).unwrap();
                }
            } else {
                let mut move_command = Command::new("mv");
                move_command.args([
                    self.saved_filename.as_ref().unwrap().as_str(),
                    if is_audio_record {
                        video_filename.as_str()
                    } else {
                        self.saved_filename.as_ref().unwrap()
                    },
                ]);
                move_command.output().unwrap();
            }

            // if audio record, then merge video and audio
            if is_audio_record && self.filename.2.active_id().unwrap().as_str() != "gif" {
                FfmpegCommand::new().input(video_filename.as_str())
                                    .format("ogg")
                                    .input(audio_filename.as_str())
                                    .args([
                                        "-c:a",
                                        "aac",
                                        self.saved_filename.as_ref().unwrap(),
                                    ])
                                    .overwrite()
                                    .spawn()
                                    .unwrap()
                                    .wait()
                                    .expect("failed to merge video and audio");

                std::fs::remove_file(audio_filename).unwrap();
            }

            std::fs::remove_file(video_filename).unwrap();
        }
        // if only audio is recording then convert it to chosen format
        else if is_audio_record {
            let mut ffmpeg_command = FfmpegCommand::new();
            ffmpeg_command.format("ogg").input(audio_filename.as_str()).arg(
                self.saved_filename.as_ref().unwrap(),
            ).spawn()
             .unwrap()
             .wait()
             .expect("failed convert audio to video");

            std::fs::remove_file(audio_filename).unwrap();
        }

        // execute command after finish recording
        if self.command.text().trim() != "" {
            Exec::shell(self.command.text().trim()).popen().unwrap();
        }
    }

    pub fn play_record(self) {
        if self.saved_filename.is_some() {
            if is_snap() {
                // open the video using snapctrl for snap package
                let snapctl = Command::new("snapctl")
                    .arg("user-open")
                    .arg(self.saved_filename.unwrap())
                    .spawn();
                match snapctl {
                    Ok(_) => {
                        // Continue
                    },
                    Err(error) => {
                        let text_buffer = TextBuffer::new(None);
                        text_buffer.set_text(&error.to_string());
                        self.error_window.set_title(Some(&get_bundle("error-title", None)));
                        self.error_window_text.set_label(&get_bundle("play-error", None));
                        self.error_details.set_buffer(Some(&text_buffer));
                        self.error_window.set_transient_for(Some(&self.window));
                        self.error_window.show();
                        self.error_window.set_hide_on_close(true);
                    },
                }
            } else {
                let open_file = open::that(self.saved_filename.unwrap());
                match open_file {
                    Ok(_) => {
                        // Continue
                    },
                    Err(error) => {
                        let text_buffer = TextBuffer::new(None);
                        text_buffer.set_text(&error.to_string());
                        self.error_window.set_title(Some(&get_bundle("error-title", None)));
                        self.error_window_text.set_label(&get_bundle("play-error", None));
                        self.error_details.set_buffer(Some(&text_buffer));
                        self.error_window.set_transient_for(Some(&self.window));
                        self.error_window.show();
                        self.error_window.set_hide_on_close(true);
                    },
                }
            }
        }
    }
}