use std::sync::{Arc, Mutex};

use psdk_father::{
  args::{PsdkArgBase, Raw},
  command::PsdkCommand,
  device::FakeConnectedDevice,
  psdk::Psdk,
  types::psdk_types::{Lifecycle, PsdkPrinterCommandEnumValue},
};
use psdk_fruit_tspl::args::*;
use psdk_fruit_tspl::marks::{
  Alignment, CodeType, Direction, ECCLevel, Font, Line, LineM, Rotation, ShowType, TImageMode,
};
use psdk_fruit_tspl::BaseTspl;

mod common;

fn string_of(build: impl FnOnce(&mut BaseTspl)) -> String {
  let mut bt = BaseTspl::new(common::lifecycle());
  build(&mut bt);
  let command = bt.command().unwrap();
  command.string().unwrap()
}

fn bytes_of(build: impl FnOnce(&mut BaseTspl)) -> Vec<u8> {
  let mut bt = BaseTspl::new(common::lifecycle());
  build(&mut bt);
  let command = bt.command().unwrap();
  command.binary().to_vec()
}

fn contains_bytes(bytes: &[u8], expected: &[u8]) -> bool {
  bytes
    .windows(expected.len())
    .any(|window| window == expected)
}

#[test]
fn test_mark_values_match_dart_extensions() {
  assert_eq!(Line::SolidLine.command_value(), "0");
  assert_eq!(Line::DottedLine.command_value(), "1");

  assert_eq!(CodeType::Code128.command_value(), "128");
  assert_eq!(CodeType::Code39.command_value(), "39");
  assert_eq!(CodeType::Code93.command_value(), "93");
  assert_eq!(CodeType::Itf.command_value(), "ITF");
  assert_eq!(CodeType::Upca.command_value(), "UPCA");
  assert_eq!(CodeType::Upce.command_value(), "UPCE");
  assert_eq!(CodeType::Codabar.command_value(), "CODABAR");
  assert_eq!(CodeType::Ean8.command_value(), "EAN8");
  assert_eq!(CodeType::Ean13.command_value(), "EAN13");
  assert_eq!(CodeType::CodeITF.command_value(), "ITF");
  assert_eq!(CodeType::CodeUPCA.command_value(), "UPCA");
  assert_eq!(CodeType::CodeUPCE.command_value(), "UPCE");
  assert_eq!(CodeType::CodeCODABAR.command_value(), "CODABAR");
  assert_eq!(CodeType::CodeEAN8.command_value(), "EAN8");
  assert_eq!(CodeType::CodeEAN13.command_value(), "EAN13");

  assert_eq!(ShowType::ShowNone.command_value(), "0");
  assert_eq!(ShowType::NoShow.command_value(), "0");
  assert_eq!(ShowType::ShowLeft.command_value(), "1");
  assert_eq!(ShowType::ShowCenter.command_value(), "2");
  assert_eq!(ShowType::ShowRight.command_value(), "3");

  assert_eq!(Rotation::Rotation0.command_value(), "0");
  assert_eq!(Rotation::Rotation90.command_value(), "90");
  assert_eq!(Rotation::Rotation180.command_value(), "180");
  assert_eq!(Rotation::Rotation270.command_value(), "270");

  assert_eq!(Direction::UpOut.command_value(), "0");
  assert_eq!(Direction::DownOut.command_value(), "1");

  assert_eq!(LineM::SolidLine.command_value(), "");
  assert_eq!(LineM::DottedLineM1.command_value(), "M1");
  assert_eq!(LineM::DottedLineM2.command_value(), "M2");
  assert_eq!(LineM::DottedLineM3.command_value(), "M3");
  assert_eq!(LineM::DottedLineM4.command_value(), "M4");

  assert_eq!(ECCLevel::LevelL.command_value(), "L");
  assert_eq!(ECCLevel::L.command_value(), "L");
  assert_eq!(ECCLevel::LevelM.command_value(), "M");
  assert_eq!(ECCLevel::M.command_value(), "M");
  assert_eq!(ECCLevel::LevelQ.command_value(), "Q");
  assert_eq!(ECCLevel::Q.command_value(), "Q");
  assert_eq!(ECCLevel::LevelH.command_value(), "H");
  assert_eq!(ECCLevel::H.command_value(), "H");

  assert_eq!(Font::Tss12.command_value(), "TSS12.BF2");
  assert_eq!(Font::Tss16.command_value(), "TSS16.BF2");
  assert_eq!(Font::Tss20.command_value(), "TSS20.BF2");
  assert_eq!(Font::Tss24.command_value(), "TSS24.BF2");
  assert_eq!(Font::Tss28.command_value(), "TSS28.BF2");
  assert_eq!(Font::Tss32.command_value(), "TSS32.BF2");
  assert_eq!(Font::TSS12.command_value(), "TSS12.BF2");
  assert_eq!(Font::TSS16.command_value(), "TSS16.BF2");
  assert_eq!(Font::TSS20.command_value(), "TSS20.BF2");
  assert_eq!(Font::TSS24.command_value(), "TSS24.BF2");
  assert_eq!(Font::TSS28.command_value(), "TSS28.BF2");
  assert_eq!(Font::TSS32.command_value(), "TSS32.BF2");

  assert_eq!(Alignment::Default.command_value(), "0");
  assert_eq!(Alignment::Left.command_value(), "1");
  assert_eq!(Alignment::Center.command_value(), "2");
  assert_eq!(Alignment::Right.command_value(), "3");

  assert_eq!(TImageMode::Overwrite.command_value(), "0");
  assert_eq!(TImageMode::Or.command_value(), "1");
  assert_eq!(TImageMode::Xor.command_value(), "2");
}

#[test]
fn test_dart_style_constructors() {
  let device = Arc::new(Mutex::new(FakeConnectedDevice::new()));
  let mut tspl = BaseTspl::with_connected_device(device);
  tspl.print(1).unwrap();
  assert_eq!(tspl.command().unwrap().string().unwrap(), "PRINT 1,1\r\n");

  let mut tspl = BaseTspl::with_lifecycle(Lifecycle::simple(Arc::new(Mutex::new(
    FakeConnectedDevice::new(),
  ))));
  tspl.cls().unwrap();
  assert_eq!(tspl.command().unwrap().string().unwrap(), "CLS\r\n");
}

#[test]
fn test_cls_matches_dart_only_header_without_trailing_space() {
  assert_eq!(
    string_of(|bt| {
      bt.cls().unwrap();
    }),
    "CLS\r\n"
  );
}

#[test]
fn test_dart_parity_cut_gap_and_status_base_methods() {
  assert_eq!(
    string_of(|bt| {
      bt.cut(true)
        .unwrap()
        .cut(false)
        .unwrap()
        .gap(true)
        .unwrap()
        .gap(false)
        .unwrap();
    }),
    "SET CUTTER 1\r\nSET CUTTER OFF\r\nSET GAP ON\r\nSET GAP OFF\r\n"
  );

  assert_eq!(
    bytes_of(|bt| {
      bt.status().unwrap();
    }),
    vec![0x1b, 0x21, 0x3f]
  );
}

#[test]
fn test_sversion_matches_dart_sdk_version() {
  let tspl = BaseTspl::new(common::lifecycle());

  assert_eq!(tspl.sversion(), "0.0.1");
}

#[test]
fn test_authors_match_dart_sdk_authors() {
  let tspl = BaseTspl::new(common::lifecycle());

  assert_eq!(
    tspl.authors(),
    vec!["jcz", "fewensa <osuni@protonmail.com>"]
  );
}

#[test]
fn test_builds_dart_fixture_commands() {
  assert_eq!(
    string_of(|bt| {
      bt.page(TPage::new(TPageOptions {
        width: 80,
        height: 40,
      }))
      .unwrap();
    }),
    "SIZE 80 mm,40 mm\r\n"
  );

  assert_eq!(
    string_of(|bt| {
      bt.qr_code(TQRCode::new(TQRCodeOptions {
        x: 10,
        y: 20,
        cell_width: 4,
        rotation: Some(Rotation::Rotation0),
        ecc_level: Some(ECCLevel::LevelH),
        version: Some("1".to_string()),
        content: "test".to_string(),
      }))
      .unwrap();
    }),
    "QRCODE 10,20,H,4,A,0,M2,S7,V1,\"test\"\r\n"
  );

  assert_eq!(
    string_of(|bt| {
      bt.print(2).unwrap();
    }),
    "PRINT 1,2\r\n"
  );

  assert_eq!(
    string_of(|bt| {
      bt.print(-2).unwrap();
    }),
    "PRINT 1,-2\r\n"
  );

  assert_eq!(
    string_of(|bt| {
      bt.print_default().unwrap();
    }),
    "PRINT 1,1\r\n"
  );
}

#[test]
fn test_dart_style_page_method_accepts_width_height_tuple() {
  assert_eq!(
    string_of(|bt| {
      bt.page((80, 40)).unwrap();
    }),
    string_of(|bt| {
      bt.page(TPage::new(TPageOptions {
        width: 80,
        height: 40,
      }))
      .unwrap();
    })
  );
}

#[test]
fn test_dart_style_print_method_accepts_default_arg() {
  assert_eq!(
    string_of(|bt| {
      bt.print(TPrint::default()).unwrap();
    }),
    string_of(|bt| {
      bt.print_default().unwrap();
    })
  );
}

#[test]
fn test_clear_resets_buffer_like_dart_sdk() {
  let mut command = BaseTspl::new(common::lifecycle());

  command.cls().unwrap();
  command.clear();

  assert_eq!(command.command().unwrap().binary(), &[]);
}

#[test]
fn test_command_keeps_buffer_until_clear_like_dart_sdk() {
  let mut command = BaseTspl::new(common::lifecycle());

  command.cls().unwrap();

  assert_eq!(command.command().unwrap().string().unwrap(), "CLS\r\n");
  assert_eq!(command.command().unwrap().string().unwrap(), "CLS\r\n");

  command.clear();

  assert_eq!(command.command().unwrap().binary(), &[]);
}

#[test]
fn test_newline_matches_dart_sdk_public_method() {
  let mut command = BaseTspl::new(common::lifecycle());

  command.cls().unwrap().newline().print_default().unwrap();

  assert_eq!(
    command.command().unwrap().string().unwrap(),
    "CLS\r\n\r\nPRINT 1,1\r\n"
  );
}

#[test]
fn test_raw_text_and_binary_constructors_match_dart_defaults() {
  let mut command = BaseTspl::new(common::lifecycle());

  command
    .raw(Raw::text("raw"))
    .unwrap()
    .raw(Raw::binary(vec![0x01, 0x02]))
    .unwrap();

  assert_eq!(command.command().unwrap().binary(), b"raw\r\n\x01\x02\r\n");
}

#[test]
fn test_raw_text_constructor_accepts_charset_like_dart() {
  let mut command = BaseTspl::new(common::lifecycle());

  command
    .raw(Raw::text_with_charset("你好", "utf-8"))
    .unwrap();

  let bytes = command.command().unwrap().binary().to_vec();
  assert!(contains_bytes(&bytes, "你好".as_bytes()));
}

#[test]
fn test_builds_ellipse_command() {
  assert_eq!(
    string_of(|bt| {
      bt.ellipse(TEllipse::new(TEllipseOptions {
        x: 1,
        y: 2,
        width: 30,
        height: 40,
        thickness: 5,
      }))
      .unwrap();
    }),
    "ELLIPSE 1,2,30,40,5\r\n"
  );
}

#[test]
fn test_builds_color_commands() {
  assert_eq!(
    string_of(|bt| {
      bt.set_red(7).unwrap().set_black().unwrap();
    }),
    "SETCOLOR RED 7\r\nSETCOLOR BLACK\r\n"
  );
}

#[test]
fn test_dart_style_auto_dot_wrapper_matches_arg_command() {
  assert_eq!(
    string_of(|bt| {
      bt.auto_dot(true).unwrap();
    }),
    string_of(|bt| {
      bt.auto_dot(TAutoDot::new(TAutoDotOptions { enable: true }))
        .unwrap();
    })
  );
}

#[test]
fn test_dart_style_paper_gap_wrappers_match_arg_commands() {
  assert_eq!(
    string_of(|bt| {
      bt.bline((3, 1))
        .unwrap()
        .continuous((0, 2))
        .unwrap()
        .label((4, 3))
        .unwrap();
    }),
    string_of(|bt| {
      bt.bline(TBline::new(TBlineOptions {
        height: Some(3),
        offset: Some(1),
      }))
      .unwrap()
      .continuous(TContinuous::new(TContinuousOptions {
        height: Some(0),
        offset: Some(2),
      }))
      .unwrap()
      .label(TLabel::new(TLabelOptions {
        height: Some(4),
        offset: Some(3),
      }))
      .unwrap();
    })
  );
}

#[test]
fn test_dart_style_default_setting_args_match_dart_constructors() {
  assert_eq!(
    string_of(|bt| {
      bt.tear(TTear::default())
        .unwrap()
        .peel(TPeel::default())
        .unwrap()
        .ribbon(TRibbon::default())
        .unwrap()
        .direction(TDirection::default())
        .unwrap();
    }),
    "SET TEAR ON\r\nSET PEEL ON\r\nSET RIBBON OFF\r\nDIRECTION 0,0\r\n"
  );
}

#[test]
fn test_dart_style_direction_method_accepts_direction_enum() {
  assert_eq!(
    string_of(|bt| {
      bt.direction(Direction::DownOut).unwrap();
    }),
    string_of(|bt| {
      bt.direction(TDirection::new(TDirectionOptions {
        direction: Direction::DownOut,
        mirror: false,
      }))
      .unwrap();
    })
  );
}

#[test]
fn test_dart_style_default_paper_and_offset_args_match_dart_constructors() {
  assert_eq!(
    string_of(|bt| {
      bt.bline(TBline::default())
        .unwrap()
        .continuous(TContinuous::default())
        .unwrap()
        .label(TLabel::default())
        .unwrap()
        .offset(TOffset::default())
        .unwrap()
        .reference_arg(TReference::default())
        .unwrap()
        .shift(TShift::default())
        .unwrap();
    }),
    concat!(
      "BLINE 3 mm,0 mm\r\n",
      "GAP 0 mm,0 mm\r\n",
      "GAP 3 mm,0 mm\r\n",
      "OFFSET 0 mm\r\n",
      "REFERENCE 0,0\r\n",
      "SHIFT 0\r\n",
    )
  );
}

#[test]
fn test_dart_style_default_paper_and_offset_methods_match_dart_calls() {
  assert_eq!(
    string_of(|bt| {
      bt.bline_default()
        .unwrap()
        .continuous_default()
        .unwrap()
        .label_default()
        .unwrap()
        .offset_default()
        .unwrap()
        .reference_default()
        .unwrap()
        .shift_default()
        .unwrap();
    }),
    concat!(
      "BLINE 3 mm,0 mm\r\n",
      "GAP 0 mm,0 mm\r\n",
      "GAP 3 mm,0 mm\r\n",
      "OFFSET 0 mm\r\n",
      "REFERENCE 0,0\r\n",
      "SHIFT 0\r\n",
    )
  );
}

#[test]
fn test_dart_style_shape_and_barcode_wrappers_use_default_args() {
  assert_eq!(
    string_of(|bt| {
      bt.bar((1, 2, 3, 4))
        .unwrap()
        .bar_code((5, 6, 7, 8, "code"))
        .unwrap()
        .line((9, 10, 11, 12, 13))
        .unwrap();
    }),
    concat!(
      "BAR 1,2,3,4,0\r\n",
      "BARCODE 5,6,\"128\",7,0,0,8,8,\"code\"\r\n",
      "LINE 9,10,11,12,13\r\n",
    )
  );
}

#[test]
fn test_dart_style_shape_methods_accept_coordinate_tuples() {
  assert_eq!(
    string_of(|bt| {
      bt.box_((1, 2, 3, 4, 5, 6))
        .unwrap()
        .circle((7, 8, 9, 10))
        .unwrap()
        .ellipse((11, 12, 13, 14, 15))
        .unwrap();
    }),
    string_of(|bt| {
      bt.box_(TBox::new(TBoxOptions {
        start_x: 1,
        start_y: 2,
        end_x: 3,
        end_y: 4,
        width: 5,
        radius: Some(6),
      }))
      .unwrap()
      .circle(TCircle::new(TCircleOptions {
        x: 7,
        y: 8,
        width: 9,
        radius: 10,
      }))
      .unwrap()
      .ellipse(TEllipse::new(TEllipseOptions {
        x: 11,
        y: 12,
        width: 13,
        height: 14,
        thickness: 15,
      }))
      .unwrap();
    })
  );
}

#[test]
fn test_dart_style_content_wrappers_use_default_args() {
  assert_eq!(
    string_of(|bt| {
      bt.qr_code((1, 2, 3, "qr", "1"))
        .unwrap()
        .text((4, 5, 1, 1, false, "txt"))
        .unwrap()
        .text_box((6, 7, 1, 1, 80, 0, false, "box"))
        .unwrap();
    }),
    concat!(
      "QRCODE 1,2,L,3,A,0,M2,S7,V1,\"qr\"\r\n",
      "TEXT 4,5,\"TSS16.BF2\",0,1,1,\"txt\"\r\n",
      "TEXTBOX 6,7,\"TSS16.BF2\",0,1,1,80,\"box\"\r\n",
    )
  );
}

#[test]
fn test_numeric_text_args_accept_signed_values_like_dart() {
  assert_eq!(
    string_of(|bt| {
      bt.speed(-1)
        .unwrap()
        .density(300)
        .unwrap()
        .set_red(-2)
        .unwrap();
    }),
    "SPEED -1\r\nDENSITY 300\r\nSETCOLOR RED -2\r\n"
  );
}

#[test]
fn test_builds_line_command_like_dart() {
  assert_eq!(
    string_of(|bt| {
      bt.line(TLine::new(TLineOptions {
        start_x: 1,
        start_y: 2,
        end_x: 30,
        end_y: 40,
        width: 3,
        line: Some(LineM::DottedLineM2),
      }))
      .unwrap();
    }),
    "LINE 1,2,30,40,3,M2\r\n"
  );
}

#[test]
fn test_line_default_solid_line_omits_empty_line_type_like_dart() {
  assert_eq!(
    string_of(|bt| {
      bt.line(TLine::new(TLineOptions {
        start_x: 1,
        start_y: 2,
        end_x: 30,
        end_y: 40,
        width: 3,
        line: None,
      }))
      .unwrap();
    }),
    "LINE 1,2,30,40,3\r\n"
  );
}

#[test]
fn test_coordinate_args_accept_signed_values_like_dart() {
  assert_eq!(
    string_of(|bt| {
      bt.bar(TBar::new(TBarOptions {
        x: -1,
        y: -2,
        width: 3,
        height: 4,
        line: Line::SolidLine,
      }))
      .unwrap()
      .line(TLine::new(TLineOptions {
        start_x: -5,
        start_y: -6,
        end_x: 7,
        end_y: -8,
        width: 2,
        line: Some(LineM::DottedLineM1),
      }))
      .unwrap()
      .box_(TBox::new(TBoxOptions {
        start_x: -9,
        start_y: -10,
        end_x: 11,
        end_y: -12,
        width: 2,
        radius: Some(0),
      }))
      .unwrap()
      .circle(TCircle::new(TCircleOptions {
        x: -13,
        y: -14,
        width: 2,
        radius: 5,
      }))
      .unwrap()
      .ellipse(TEllipse::new(TEllipseOptions {
        x: -15,
        y: -16,
        width: 30,
        height: 40,
        thickness: 5,
      }))
      .unwrap()
      .text(TText::new(TTextOptions {
        x: -17,
        y: -18,
        content: "Hi".to_string(),
        font: Some(Font::TSS16),
        rotation: Some(Rotation::Rotation0),
        x_multi: Some(1),
        y_multi: Some(1),
        bold: Some(false),
        alignment: Some(Alignment::Default),
        raw_font: None,
      }))
      .unwrap()
      .bar_code(TBarCode::new(TBarCodeOptions {
        x: -19,
        y: -20,
        height: 30,
        code_type: Some(CodeType::Code128),
        show_type: Some(ShowType::ShowNone),
        rotation: Some(Rotation::Rotation0),
        cell_width: 2,
        ratio: Some(1),
        content: "bc".to_string(),
      }))
      .unwrap()
      .qr_code(TQRCode::new(TQRCodeOptions {
        x: -21,
        y: -22,
        cell_width: 4,
        rotation: Some(Rotation::Rotation0),
        ecc_level: Some(ECCLevel::LevelH),
        version: Some("1".to_string()),
        content: "qr".to_string(),
      }))
      .unwrap()
      .dmatrix(TDmatrix::new(TDmatrixOptions {
        x: -23,
        y: -24,
        width: 10,
        height: 12,
        content: "dm".to_string(),
      }))
      .unwrap()
      .text_box(TTextBox::new(TTextBoxOptions {
        x: -25,
        y: -26,
        content: "box".to_string(),
        width: 100,
        font: Some(Font::TSS16),
        rotation: Some(Rotation::Rotation0),
        x_multi: Some(1),
        y_multi: Some(1),
        line_space: 0,
        rotation_type: false,
        bold: Some(false),
      }))
      .unwrap()
      .reverse(TReverse::new(TReverseOptions {
        x: -27,
        y: -28,
        height: 8,
        width: 9,
      }))
      .unwrap()
      .put_image(TPutImage::new(TPutImageOptions {
        x: -29,
        y: -30,
        file_name: "a.bmp".to_string(),
      }))
      .unwrap();
    }),
    concat!(
      "BAR -1,-2,3,4,0\r\n",
      "LINE -5,-6,7,-8,2,M1\r\n",
      "BOX -9,-10,11,-12,2,0\r\n",
      "CIRCLE -13,-14,5,2\r\n",
      "ELLIPSE -15,-16,30,40,5\r\n",
      "TEXT -17,-18,\"TSS16.BF2\",0,1,1,\"Hi\"\r\n",
      "BARCODE -19,-20,\"128\",30,0,0,2,2,\"bc\"\r\n",
      "QRCODE -21,-22,H,4,A,0,M2,S7,V1,\"qr\"\r\n",
      "DMATRIX -23,-24,10,12,\"dm\"\r\n",
      "TEXTBOX -25,-26,\"TSS16.BF2\",0,1,1,100,\"box\"\r\n",
      "REVERSE -27,-28,9,8\r\n",
      "PUTBMP -29,-30,\"a.bmp\"\r\n",
    )
  );
}

#[test]
fn test_builds_text_command_like_dart() {
  assert_eq!(
    string_of(|bt| {
      bt.text(TText::new(TTextOptions {
        x: 10,
        y: 20,
        content: "Hello".to_string(),
        font: Some(Font::TSS16),
        rotation: Some(Rotation::Rotation0),
        x_multi: Some(1),
        y_multi: Some(1),
        bold: Some(false),
        alignment: Some(Alignment::Default),
        raw_font: None,
      }))
      .unwrap();
    }),
    "TEXT 10,20,\"TSS16.BF2\",0,1,1,\"Hello\"\r\n"
  );
}

#[test]
fn test_text_uses_raw_font_like_dart() {
  assert_eq!(
    string_of(|bt| {
      bt.text(TText::new(TTextOptions {
        x: 10,
        y: 20,
        content: "Hello".to_string(),
        font: Some(Font::TSS16),
        raw_font: Some("CUSTOM.FONT".to_string()),
        rotation: Some(Rotation::Rotation0),
        x_multi: Some(1),
        y_multi: Some(1),
        bold: Some(false),
        alignment: Some(Alignment::Default),
      }))
      .unwrap();
    }),
    "TEXT 10,20,\"CUSTOM.FONT\",0,1,1,\"Hello\"\r\n"
  );
}

#[test]
fn test_builds_ribbon_end_commands() {
  assert_eq!(
    string_of(|bt| {
      bt.set_ribbon_end(true).unwrap().get_ribbon_end().unwrap();
    }),
    "SET RIBBONEND ON\r\nDIAGNOSTIC REPORT RIBBONEND\r\n"
  );
}

#[test]
fn test_state_keeps_required_dart_trailing_space() {
  assert_eq!(TReadState::new().header(), "READSTA ");

  assert_eq!(
    string_of(|bt| {
      bt.state().unwrap();
    }),
    "READSTA \r\n"
  );
}

#[test]
fn test_only_header_read_commands_do_not_add_extra_trailing_space() {
  assert_eq!(
    string_of(|bt| {
      bt.mddle().unwrap().sn().unwrap().version().unwrap();
    }),
    "READC MDDLE\r\nREADC PRDOCUTID\r\nREADC VERSION\r\n"
  );
}

#[test]
fn test_builds_readback_and_maintenance_commands() {
  assert_eq!(
    string_of(|bt| {
      bt.self_test()
        .unwrap()
        .versions()
        .unwrap()
        .models()
        .unwrap()
        .sns()
        .unwrap()
        .set_gbk_code()
        .unwrap()
        .set_utf8_code()
        .unwrap()
        .learn_paper_gap()
        .unwrap()
        .read_hardware_version()
        .unwrap()
        .reset_to_factory()
        .unwrap();
    }),
    concat!(
      "SELFTEST\r\n",
      "OUT GETSETTING$(\"SYSTEM\",\"INFORMATION\",\"VERSION\")\r\n",
      "OUT GETSETTING$(\"SYSTEM\",\"INFORMATION\",\"MODEL\")\r\n",
      "OUT GETSETTING$(\"SYSTEM\",\"INFORMATION\",\"SERIAL\")\r\n",
      "CODEPAGE 936\r\n",
      "CODEPAGE UTF-8\r\n",
      "GAPDETECT\r\n",
      "READC HW_VERSION\r\n",
      "RESET FACTORY\r\n",
    )
  );
}

#[test]
fn test_uses_paper_gap_offset() {
  assert_eq!(
    string_of(|bt| {
      bt.bline(TBline::new(TBlineOptions {
        height: Some(12),
        offset: Some(2),
      }))
      .unwrap();
    }),
    "BLINE 12 mm,2 mm\r\n"
  );

  assert_eq!(
    string_of(|bt| {
      bt.continuous(TContinuous::new(TContinuousOptions {
        height: Some(12),
        offset: Some(2),
      }))
      .unwrap();
    }),
    "GAP 12 mm,2 mm\r\n"
  );

  assert_eq!(
    string_of(|bt| {
      bt.label(TLabel::new(TLabelOptions {
        height: Some(8),
        offset: Some(3),
      }))
      .unwrap();
    }),
    "GAP 8 mm,3 mm\r\n"
  );
}

#[test]
fn test_size_args_accept_signed_values_like_dart() {
  assert_eq!(
    string_of(|bt| {
      bt.page(TPage::new(TPageOptions {
        width: -80,
        height: 40,
      }))
      .unwrap()
      .bar(TBar::new(TBarOptions {
        x: 1,
        y: 2,
        width: -3,
        height: 4,
        line: Line::SolidLine,
      }))
      .unwrap()
      .line(TLine::new(TLineOptions {
        start_x: 1,
        start_y: 2,
        end_x: 3,
        end_y: 4,
        width: -5,
        line: None,
      }))
      .unwrap()
      .box_(TBox::new(TBoxOptions {
        start_x: 1,
        start_y: 2,
        end_x: 3,
        end_y: 4,
        width: -6,
        radius: Some(-7),
      }))
      .unwrap()
      .circle(TCircle::new(TCircleOptions {
        x: 1,
        y: 2,
        width: -8,
        radius: -9,
      }))
      .unwrap()
      .ellipse(TEllipse::new(TEllipseOptions {
        x: 1,
        y: 2,
        width: -10,
        height: 11,
        thickness: -12,
      }))
      .unwrap()
      .reverse(TReverse::new(TReverseOptions {
        x: 1,
        y: 2,
        width: -13,
        height: 14,
      }))
      .unwrap()
      .dmatrix(TDmatrix::new(TDmatrixOptions {
        x: 1,
        y: 2,
        width: -15,
        height: 16,
        content: "dm".to_string(),
      }))
      .unwrap()
      .qr_code(TQRCode::new(TQRCodeOptions {
        x: 1,
        y: 2,
        cell_width: -17,
        rotation: None,
        ecc_level: None,
        version: None,
        content: "qr".to_string(),
      }))
      .unwrap()
      .bar_code(TBarCode::new(TBarCodeOptions {
        x: 1,
        y: 2,
        height: -18,
        code_type: None,
        show_type: None,
        rotation: None,
        cell_width: -19,
        ratio: Some(1),
        content: "bc".to_string(),
      }))
      .unwrap()
      .text(TText::new(TTextOptions {
        x: 1,
        y: 2,
        content: "txt".to_string(),
        font: None,
        rotation: None,
        x_multi: Some(-20),
        y_multi: Some(0),
        bold: None,
        alignment: None,
        raw_font: None,
      }))
      .unwrap()
      .text_box(TTextBox::new(TTextBoxOptions {
        x: 1,
        y: 2,
        content: "box".to_string(),
        width: -21,
        font: None,
        rotation: None,
        x_multi: Some(-22),
        y_multi: Some(0),
        line_space: -23,
        rotation_type: false,
        bold: None,
      }))
      .unwrap()
      .clean_flash(-24)
      .unwrap()
      .bline(TBline::new(TBlineOptions {
        height: Some(-25),
        offset: Some(-26),
      }))
      .unwrap()
      .continuous(TContinuous::new(TContinuousOptions {
        height: Some(-27),
        offset: Some(-28),
      }))
      .unwrap()
      .label(TLabel::new(TLabelOptions {
        height: Some(-29),
        offset: Some(-30),
      }))
      .unwrap();
    }),
    concat!(
      "SIZE -80 mm,40 mm\r\n",
      "BAR 1,2,-3,4,0\r\n",
      "LINE 1,2,3,4,-5\r\n",
      "BOX 1,2,3,4,-6,-7\r\n",
      "CIRCLE 1,2,-9,-8\r\n",
      "ELLIPSE 1,2,-10,11,-12\r\n",
      "REVERSE 1,2,-13,14\r\n",
      "DMATRIX 1,2,-15,16,\"dm\"\r\n",
      "QRCODE 1,2,L,-17,A,0,M2,S7,V,\"qr\"\r\n",
      "BARCODE 1,2,\"128\",-18,0,0,-19,-19,\"bc\"\r\n",
      "TEXT 1,2,\"TSS16.BF2\",0,-20,0,\"txt\"\r\n",
      "TEXTBOX 1,2,\"TSS16.BF2\",0,-22,0,-21,\"box\"\r\n",
      "BMPDELETE -24\r\n",
      "BLINE -25 mm,-26 mm\r\n",
      "GAP -27 mm,-28 mm\r\n",
      "GAP -29 mm,-30 mm\r\n"
    )
  );
}

#[test]
fn test_signed_offsets_match_dart_int_arguments() {
  assert_eq!(
    string_of(|bt| {
      bt.bline(TBline::new(TBlineOptions {
        height: Some(12),
        offset: Some(-2),
      }))
      .unwrap();
    }),
    "BLINE 12 mm,-2 mm\r\n"
  );

  assert_eq!(
    string_of(|bt| {
      bt.continuous(TContinuous::new(TContinuousOptions {
        height: Some(12),
        offset: Some(-2),
      }))
      .unwrap();
    }),
    "GAP 12 mm,-2 mm\r\n"
  );

  assert_eq!(
    string_of(|bt| {
      bt.label(TLabel::new(TLabelOptions {
        height: Some(8),
        offset: Some(-3),
      }))
      .unwrap();
    }),
    "GAP 8 mm,-3 mm\r\n"
  );

  assert_eq!(
    string_of(|bt| {
      bt.offset(-4).unwrap();
    }),
    "OFFSET -4 mm\r\n"
  );

  assert_eq!(
    string_of(|bt| {
      bt.shift(-5).unwrap();
    }),
    "SHIFT -5\r\n"
  );

  assert_eq!(
    string_of(|bt| {
      bt.reference(-6, -7).unwrap();
    }),
    "REFERENCE -6,-7\r\n"
  );
}

#[test]
fn test_qrcode_escapes_like_dart() {
  assert_eq!(
    string_of(|bt| {
      bt.qr_code(TQRCode::new(TQRCodeOptions {
        x: 10,
        y: 20,
        cell_width: 4,
        rotation: Some(Rotation::Rotation90),
        ecc_level: Some(ECCLevel::LevelM),
        version: Some("7".to_string()),
        content: "a\"b\r\nc".to_string(),
      }))
      .unwrap();
    }),
    "QRCODE 10,20,M,4,A,90,M2,S7,V7,\"a\\[\"]b  c\"\r\n"
  );
}

#[test]
fn test_qrcode_none_version_keeps_dart_version_field_shape() {
  assert_eq!(
    string_of(|bt| {
      bt.qr_code(TQRCode::new(TQRCodeOptions {
        x: 1,
        y: 2,
        cell_width: 3,
        rotation: None,
        ecc_level: None,
        version: None,
        content: "qr".to_string(),
      }))
      .unwrap();
    }),
    "QRCODE 1,2,L,3,A,0,M2,S7,V,\"qr\"\r\n"
  );
}

#[test]
fn test_qrcode_uses_utf8_charset_like_dart() {
  let bytes = bytes_of(|bt| {
    bt.qr_code(TQRCode::new(TQRCodeOptions {
      x: 10,
      y: 20,
      cell_width: 4,
      rotation: None,
      ecc_level: None,
      version: Some("1".to_string()),
      content: "你好".to_string(),
    }))
    .unwrap();
  });

  assert!(contains_bytes(&bytes, "你好".as_bytes()));
}

#[test]
fn test_text_uses_default_gbk_charset_like_dart() {
  let bytes = bytes_of(|bt| {
    bt.text(TText::new(TTextOptions {
      x: 1,
      y: 2,
      font: None,
      raw_font: None,
      rotation: None,
      x_multi: Some(1),
      y_multi: Some(1),
      bold: Some(false),
      alignment: None,
      content: "你好".to_string(),
    }))
    .unwrap();
  });

  assert!(contains_bytes(&bytes, &[0xc4, 0xe3, 0xba, 0xc3]));
  assert!(!contains_bytes(&bytes, "你好".as_bytes()));
}

#[test]
fn test_text_box_omits_zero_line_space_like_dart() {
  assert_eq!(
    string_of(|bt| {
      bt.text_box(TTextBox::new(TTextBoxOptions {
        x: 1,
        y: 2,
        content: "hello".to_string(),
        width: 100,
        font: Some(Font::TSS16),
        rotation: Some(Rotation::Rotation0),
        x_multi: Some(1),
        y_multi: Some(1),
        line_space: 0,
        rotation_type: false,
        bold: Some(false),
      }))
      .unwrap();
    }),
    "TEXTBOX 1,2,\"TSS16.BF2\",0,1,1,100,\"hello\"\r\n"
  );
}

#[test]
fn test_text_box_rotation_type_matches_dart_enum_string() {
  assert_eq!(
    string_of(|bt| {
      bt.text_box(TTextBox::new(TTextBoxOptions {
        x: 1,
        y: 2,
        content: "hello".to_string(),
        width: 100,
        font: Some(Font::TSS16),
        rotation: Some(Rotation::Rotation90),
        x_multi: Some(1),
        y_multi: Some(1),
        line_space: 0,
        rotation_type: true,
        bold: Some(false),
      }))
      .unwrap();
    }),
    "TEXTBOX 1,2,\"TSS16.BF2\",90,1,1,100,DTRotation.rotation_90,\"hello\"\r\n"
  );
}

#[test]
fn test_image_alias_matches_image_pixels() {
  let options = TImagePixelsOptions {
    x: 1,
    y: 2,
    pixels: {
      let mut pixels = Vec::new();
      pixels.extend_from_slice(&[0x00, 0x00, 0x00, 0xff]);
      for _ in 1..8 {
        pixels.extend_from_slice(&[0xff, 0xff, 0xff, 0xff]);
      }
      pixels
    },
    width: 8,
    height: 1,
    compress: false,
    reverse: false,
    gray_threshold: None,
    smooth_weight: 0,
    edge_dither_mode: None,
    edge_dither_depth: Default::default(),
    image_mode: TImageMode::Overwrite,
  };

  assert_eq!(
    bytes_of(|bt| {
      bt.image(TImagePixels::new(options.clone())).unwrap();
    }),
    bytes_of(|bt| {
      bt.image_pixels(TImagePixels::new(options)).unwrap();
    })
  );
}

#[test]
fn test_dart_style_image_pixel_wrapper_uses_default_args() {
  let pixels = vec![0x00, 0x00, 0x00, 0xff];

  assert_eq!(
    bytes_of(|bt| {
      bt.image((1, 2, pixels.clone(), 1, 1))
        .unwrap()
        .image_pixels((3, 4, pixels.clone(), 1, 1))
        .unwrap();
    }),
    bytes_of(|bt| {
      bt.image(TImagePixels::new(TImagePixelsOptions {
        x: 1,
        y: 2,
        pixels: pixels.clone(),
        width: 1,
        height: 1,
        compress: false,
        reverse: false,
        gray_threshold: None,
        smooth_weight: 0,
        edge_dither_mode: None,
        edge_dither_depth: Default::default(),
        image_mode: TImageMode::Overwrite,
      }))
      .unwrap()
      .image_pixels(TImagePixels::new(TImagePixelsOptions {
        x: 3,
        y: 4,
        pixels,
        width: 1,
        height: 1,
        compress: false,
        reverse: false,
        gray_threshold: None,
        smooth_weight: 0,
        edge_dither_mode: None,
        edge_dither_depth: Default::default(),
        image_mode: TImageMode::Overwrite,
      }))
      .unwrap();
    })
  );
}

#[test]
fn test_dart_style_matrix_reverse_and_put_image_methods_accept_tuples() {
  assert_eq!(
    string_of(|bt| {
      bt.dmatrix((1, 2, 3, 4, "dm"))
        .unwrap()
        .reverse((5, 6, 7, 8))
        .unwrap()
        .put_image((9, 10, "logo.bmp"))
        .unwrap();
    }),
    string_of(|bt| {
      bt.dmatrix(TDmatrix::new(TDmatrixOptions {
        x: 1,
        y: 2,
        width: 3,
        height: 4,
        content: "dm".to_string(),
      }))
      .unwrap()
      .reverse(TReverse::new(TReverseOptions {
        x: 5,
        y: 6,
        height: 7,
        width: 8,
      }))
      .unwrap()
      .put_image(TPutImage::new(TPutImageOptions {
        x: 9,
        y: 10,
        file_name: "logo.bmp".to_string(),
      }))
      .unwrap();
    })
  );
}

#[cfg(feature = "image-plus")]
#[test]
fn test_dart_style_image_bytes_wrapper_uses_default_args() {
  let image = include_bytes!("../../../../docs/resources/1px.png").to_vec();

  assert_eq!(
    bytes_of(|bt| {
      bt.image_bytes((1, 2, image.clone())).unwrap();
    }),
    bytes_of(|bt| {
      bt.image_bytes(
        TImageBytes::new(TImageBytesOptions {
          x: 1,
          y: 2,
          bytes: image,
          compress: false,
          reverse: false,
          gray_threshold: None,
          smooth_weight: 0,
          edge_dither_mode: None,
          edge_dither_depth: Default::default(),
          image_mode: TImageMode::Overwrite,
        })
        .unwrap(),
      )
      .unwrap();
    })
  );
}

#[test]
fn test_image_uses_dart_default_overwrite_mode() {
  let bytes = bytes_of(|bt| {
    bt.image(TImagePixels::new(TImagePixelsOptions {
      x: 1,
      y: 2,
      pixels: vec![0x00, 0x00, 0x00, 0xff],
      width: 1,
      height: 1,
      compress: false,
      reverse: false,
      gray_threshold: None,
      smooth_weight: 0,
      edge_dither_mode: None,
      edge_dither_depth: Default::default(),
      image_mode: TImageMode::Overwrite,
    }))
    .unwrap();
  });

  assert_eq!(bytes, b"BITMAP 1,2,1,1,0,\x7f\r\n");
}

#[test]
fn test_image_coordinate_args_accept_signed_values_like_dart() {
  let bytes = bytes_of(|bt| {
    bt.image(TImagePixels::new(TImagePixelsOptions {
      x: -1,
      y: -2,
      pixels: vec![0x00, 0x00, 0x00, 0xff],
      width: 1,
      height: 1,
      compress: false,
      reverse: false,
      gray_threshold: None,
      smooth_weight: 0,
      edge_dither_mode: None,
      edge_dither_depth: Default::default(),
      image_mode: TImageMode::Overwrite,
    }))
    .unwrap();
  });

  assert_eq!(bytes, b"BITMAP -1,-2,1,1,0,\x7f\r\n");
}

#[test]
fn test_image_supports_dart_image_modes() {
  let bytes = bytes_of(|bt| {
    bt.image(TImagePixels::new(TImagePixelsOptions {
      x: 1,
      y: 2,
      pixels: vec![0x00, 0x00, 0x00, 0xff],
      width: 1,
      height: 1,
      compress: false,
      reverse: false,
      gray_threshold: None,
      smooth_weight: 0,
      edge_dither_mode: None,
      edge_dither_depth: Default::default(),
      image_mode: TImageMode::Xor,
    }))
    .unwrap();
  });

  assert!(bytes.starts_with(b"BITMAP 1,2,1,1,2,"));
}

#[test]
fn test_image_uses_custom_gray_threshold() {
  let bytes = bytes_of(|bt| {
    bt.image(TImagePixels::new(TImagePixelsOptions {
      x: 1,
      y: 2,
      pixels: vec![150, 150, 150, 255],
      width: 1,
      height: 1,
      compress: false,
      reverse: false,
      gray_threshold: Some(100),
      smooth_weight: 0,
      edge_dither_mode: None,
      edge_dither_depth: Default::default(),
      image_mode: TImageMode::Overwrite,
    }))
    .unwrap();
  });

  assert_eq!(bytes, b"BITMAP 1,2,1,1,0,\xff\r\n");
}

#[test]
fn test_image_supports_dart_reverse_arg() {
  let bytes = bytes_of(|bt| {
    bt.image(TImagePixels::new(TImagePixelsOptions {
      x: 1,
      y: 2,
      pixels: vec![0, 0, 0, 255],
      width: 1,
      height: 1,
      compress: false,
      reverse: true,
      gray_threshold: Some(128),
      smooth_weight: 0,
      edge_dither_mode: None,
      edge_dither_depth: Default::default(),
      image_mode: TImageMode::Overwrite,
    }))
    .unwrap();
  });

  assert_eq!(bytes, b"BITMAP 1,2,1,1,0,\xff\r\n");
}

#[test]
fn test_image_supports_dart_smooth_and_edge_dither_args() {
  let bytes = bytes_of(|bt| {
    bt.image(TImagePixels::new(TImagePixelsOptions {
      x: 1,
      y: 2,
      pixels: vec![
        120, 120, 120, 255, 120, 120, 120, 255, 120, 120, 120, 255, 255, 255, 255, 255,
      ],
      width: 4,
      height: 1,
      compress: false,
      reverse: false,
      gray_threshold: Some(128),
      smooth_weight: 0,
      edge_dither_mode: Some(ThresholdEdgeDitherMode::Floyd),
      edge_dither_depth: ThresholdEdgeDitherDepth::Deep,
      image_mode: TImageMode::Overwrite,
    }))
    .unwrap();
  });

  assert_eq!(bytes, b"BITMAP 1,2,1,1,0,\x5f\r\n");
}
