use embedded_graphics::pixelcolor::Rgb565;
use embedded_hal::delay::DelayNs;
use mipidsi::{
    ConfigurationError,
    dcs::{
        BitsPerPixel, ExitSleepMode, InterfaceExt, PixelFormat, SetAddressMode, SetDisplayOn,
        SetInvertMode, SetPixelFormat, SetTearingEffect,
    },
    interface::{Interface, InterfaceKind},
    models::{Model, ModelInitError},
    options::{ColorInversion, ModelOptions, TearingEffect},
};

/// JD9853 display driver in Rgb565 color mode.
///
/// The JD9853 has a 240-column internal framebuffer but the visible panel is
/// 172×320, starting at column offset 34.  Configure the [`Builder`] as:
///
/// ```ignore
/// Builder::new(JD9853, di)
///     .display_size(172, 320)
///     .display_offset(34, 0)
/// ```
///
/// This panel requires colour inversion; the driver always enables it.
pub struct JD9853;

impl Model for JD9853 {
    type ColorFormat = Rgb565;

    // Full internal framebuffer of the JD9853 controller.
    // Visible area is 172×320 at column offset 34 – set in the Builder.
    const FRAMEBUFFER_SIZE: (u16, u16) = (240, 320);

    fn init<DELAY, DI>(
        &mut self,
        di: &mut DI,
        delay: &mut DELAY,
        options: &ModelOptions,
    ) -> Result<SetAddressMode, ModelInitError<DI::Error>>
    where
        DELAY: DelayNs,
        DI: Interface,
    {
        if !matches!(
            DI::KIND,
            InterfaceKind::Serial4Line
                | InterfaceKind::Parallel8Bit
                | InterfaceKind::Parallel16Bit
        ) {
            return Err(ModelInitError::InvalidConfiguration(
                ConfigurationError::UnsupportedInterface,
            ));
        }

        let madctl = SetAddressMode::from(options);

        // --- Exit sleep and wait for internal oscillator ---
        di.write_command(ExitSleepMode)?;
        delay.delay_us(120_000);

        // --- Unlock manufacturer commands ---
        di.write_raw(0xDF, &[0x98, 0x53])?;

        // --- Bank 0: panel / power / gamma settings ---
        di.write_raw(0xB2, &[0x23])?;
        di.write_raw(0xB7, &[0x00, 0x47, 0x00, 0x6F])?;
        di.write_raw(0xBB, &[0x1C, 0x1A, 0x55, 0x73, 0x63, 0xF0])?;
        di.write_raw(0xC0, &[0x44, 0xA4])?;
        di.write_raw(0xC1, &[0x16])?;
        di.write_raw(0xC3, &[0x7D, 0x07, 0x14, 0x06, 0xCF, 0x71, 0x72, 0x77])?;
        di.write_raw(
            0xC4,
            &[
                0x00, 0x00, 0xA0, 0x79, 0x0B, 0x0A, 0x16, 0x79, 0x0B, 0x0A, 0x16, 0x82,
            ],
        )?;
        // Gamma table (positive + negative, 16 bytes each)
        di.write_raw(
            0xC8,
            &[
                0x3F, 0x32, 0x29, 0x29, 0x27, 0x2B, 0x27, 0x28,
                0x28, 0x26, 0x25, 0x17, 0x12, 0x0D, 0x04, 0x00,
                0x3F, 0x32, 0x29, 0x29, 0x27, 0x2B, 0x27, 0x28,
                0x28, 0x26, 0x25, 0x17, 0x12, 0x0D, 0x04, 0x00,
            ],
        )?;
        di.write_raw(0xD0, &[0x04, 0x06, 0x6B, 0x0F, 0x00])?;
        di.write_raw(0xD7, &[0x00, 0x30])?;
        di.write_raw(0xE6, &[0x14])?;

        // --- Bank 1: power settings ---
        di.write_raw(0xDE, &[0x01])?;
        di.write_raw(0xB7, &[0x03, 0x13, 0xEF, 0x35, 0x35])?;
        di.write_raw(0xC1, &[0x14, 0x15, 0xC0])?;
        di.write_raw(0xC2, &[0x06, 0x3A])?;
        di.write_raw(0xC4, &[0x72, 0x12])?;
        di.write_raw(0xBE, &[0x00])?;

        // --- Bank 2: internal settings (first pass) ---
        di.write_raw(0xDE, &[0x02])?;
        di.write_raw(0xE5, &[0x00, 0x02, 0x00])?;
        di.write_raw(0xE5, &[0x01, 0x02, 0x00])?;

        // --- Return to bank 0 ---
        di.write_raw(0xDE, &[0x00])?;

        // --- Standard DCS: tearing effect + pixel format ---
        di.write_command(SetTearingEffect::new(TearingEffect::Vertical))?;

        let pf = PixelFormat::with_all(BitsPerPixel::from_rgb_color::<Self::ColorFormat>());
        di.write_command(SetPixelFormat::new(pf))?;

        // Seed the column / page address window (mipidsi will update this before
        // each draw, but the second bank-2 block below requires these to have been
        // written first to latch internal timing registers).
        di.write_raw(0x2A, &[0x00, 0x22, 0x00, 0xCD])?; // cols 34..205
        di.write_raw(0x2B, &[0x00, 0x00, 0x01, 0x3F])?; // rows 0..319

        // --- Bank 2: internal settings (second pass, after window init) ---
        di.write_raw(0xDE, &[0x02])?;
        di.write_raw(0xE5, &[0x00, 0x02, 0x00])?;
        di.write_raw(0xDE, &[0x00])?;

        // --- Address mode + colour inversion ---
        di.write_command(madctl)?;

        // This panel always requires colour inversion to display correct colours.
        di.write_command(SetInvertMode::new(ColorInversion::Inverted))?;

        // 10 ms settle time before display-on (matches reference init ordering).
        delay.delay_us(10_000);

        di.write_command(SetDisplayOn)?;

        Ok(madctl)
    }
}
