use self::swc_ecma_parser::{Parser, Session, SourceFileInput, Syntax};
use super::*;
use crate::config::Config;
use std::{
    fmt::{self, Debug, Display, Formatter},
    io::Write,
    sync::{Arc, RwLock},
};
use swc_common::{comments::Comments, FileName, SourceMap};
use swc_ecma_parser;

struct Noop;
impl Handlers for Noop {}

struct Builder {
    cfg: Config,
    cm: Arc<SourceMap>,
    comments: Comments,
}

impl Builder {
    pub fn with<F, Ret>(self, src: &str, s: &mut Vec<u8>, op: F) -> Ret
    where
        F: FnOnce(&mut Emitter<'_>) -> Ret,
    {
        let mut e = Emitter {
            cfg: self.cfg,
            cm: self.cm.clone(),
            wr: Box::new(text_writer::JsWriter::new(self.cm.clone(), "\n", s, None)),
            comments: Some(&self.comments),
            handlers: Box::new(Noop),
        };

        let ret = op(&mut e);

        ret
    }

    pub fn text<F>(self, src: &str, op: F) -> String
    where
        F: FnOnce(&mut Emitter<'_>),
    {
        let mut buf = vec![];

        self.with(src, &mut buf, op);

        String::from_utf8(buf).unwrap()
    }
}

fn parse_then_emit(from: &str, cfg: Config) -> String {
    ::testing::run_test(false, |cm, handler| {
        let src = cm.new_source_file(FileName::Real("custom.js".into()), from.to_string());
        println!(
            "--------------------\nSource: \n{}\nPos: {:?} ~ {:?}\n",
            from, src.start_pos, src.end_pos
        );

        let comments = Default::default();
        let res = {
            let mut parser = Parser::new(
                Session { handler: &handler },
                Syntax::default(),
                SourceFileInput::from(&*src),
                Some(&comments),
            );
            parser.parse_module().map_err(|mut e| {
                e.emit();
            })?
        };

        let out = Builder { cfg, cm, comments }.text(from, |e| e.emit_module(&res).unwrap());
        Ok(out)
    })
    .unwrap()
}

pub(crate) fn assert_min(from: &str, to: &str) {
    let out = parse_then_emit(from, Config { minify: true });

    assert_eq!(DebugUsingDisplay(out.trim()), DebugUsingDisplay(to),);
}

pub(crate) fn assert_pretty(from: &str, to: &str) {
    let out = parse_then_emit(from, Config { minify: false });

    assert_eq!(DebugUsingDisplay(&out.trim()), DebugUsingDisplay(to),);
}

fn test_from_to(from: &str, to: &str) {
    let out = parse_then_emit(from, Default::default());

    assert_eq!(DebugUsingDisplay(out.trim()), DebugUsingDisplay(to.trim()),);
}

#[test]
fn empty_stmt() {
    test_from_to(";", ";");
}

#[test]
fn comment_1() {
    test_from_to(
        "// foo
a",
        "// foo
a;",
    );
}

#[test]
fn comment_2() {
    test_from_to("a // foo", "a; // foo");
}

#[test]
fn comment_3() {
    test_from_to(
        "// foo
// bar
a
// foo
b // bar",
        "// foo
// bar
a;
// foo
b; // bar",
    );
}

#[test]
fn comment_4() {
    test_from_to(
        "/** foo */
a",
        "/** foo */
a;",
    );
}

#[test]
fn comment_5() {
    test_from_to(
        "// foo
// bar
a",
        "// foo
// bar
a;",
    );
}

#[test]
fn no_octal_escape() {
    test_from_to(
        r#"'\x00a';
'\x000';
'\x001';
'\x009'"#,
        r#"'\0a';
'\x000';
'\x001';
'\x009';"#,
    );
}

#[test]
fn issue_450() {
    test_from_to(
        r#"console.log(`
\`\`\`html
<h1>It works!</h1>
\`\`\`
`)"#,
        r#"console.log(`
\`\`\`html
<h1>It works!</h1>
\`\`\`
`);"#,
    );
}

#[test]
fn issue_546() {
    test_from_to(
        "import availabilities, * as availabilityFunctions from 'reducers/availabilities';",
        "import availabilities, * as availabilityFunctions from 'reducers/availabilities';",
    );
}

#[derive(Debug, Clone)]
struct Buf(Arc<RwLock<Vec<u8>>>);
impl Write for Buf {
    fn write(&mut self, data: &[u8]) -> io::Result<usize> {
        self.0.write().unwrap().write(data)
    }

    fn flush(&mut self) -> io::Result<()> {
        self.0.write().unwrap().flush()
    }
}

#[derive(PartialEq, Eq)]
struct DebugUsingDisplay<'a>(&'a str);

impl<'a> Debug for DebugUsingDisplay<'a> {
    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
        Display::fmt(self.0, f)
    }
}
