use embedded_hal::i2c::I2c;
use heapless::Vec;

pub const FT6336U_ADDRESS: u8 = 0x38;

#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
pub struct TouchPoint {
    pub id: u8,
    pub x: u16,
    pub y: u16,
}

#[derive(Clone, Debug, Default, Eq, PartialEq)]
pub struct TouchState {
    pub points: Vec<TouchPoint, 2>,
}

impl TouchState {
    /// Returns true when at least one touch point is active.
    #[must_use]
    pub fn is_pressed(&self) -> bool {
        !self.points.is_empty()
    }

    #[must_use]
    /// Returns the first active touch point.
    pub fn primary(&self) -> Option<TouchPoint> {
        self.points.first().copied()
    }
}

#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum TouchEvent {
    Pressed(TouchPoint),
    Released,
    Moved(TouchPoint),
    Idle,
}

pub struct Touch<I2C> {
    i2c: I2C,
    address: u8,
    previous: TouchState,
}

impl<I2C> Touch<I2C> {
    /// Creates an FT6336U touch driver on the default Nesso N1 I2C address.
    #[must_use]
    pub const fn new(i2c: I2C) -> Self {
        Self {
            i2c,
            address: FT6336U_ADDRESS,
            previous: TouchState { points: Vec::new() },
        }
    }

    /// Releases the wrapped I2C bus.
    pub fn release(self) -> I2C {
        self.i2c
    }
}

impl<I2C, E> Touch<I2C>
where
    I2C: I2c<Error = E>,
{
    /// Reads the current touch points from the controller.
    pub fn read_state(&mut self) -> Result<TouchState, E> {
        let mut count = [0u8; 1];
        self.i2c.write_read(self.address, &[0x02], &mut count)?;
        let touches = count[0] & 0x0f;
        let mut state = TouchState::default();
        if touches > 0 {
            let mut data = [0u8; 4];
            self.i2c.write_read(self.address, &[0x03], &mut data)?;
            let x = (u16::from(data[0] & 0x0f) << 8) | u16::from(data[1]);
            let y = (u16::from(data[2] & 0x0f) << 8) | u16::from(data[3]);
            let _ = state.points.push(TouchPoint { id: 0, x, y });
        }
        Ok(state)
    }

    /// Polls the controller and returns the state transition since the last poll.
    pub fn poll_event(&mut self) -> Result<TouchEvent, E> {
        let current = self.read_state()?;
        let event = match (self.previous.primary(), current.primary()) {
            (None, Some(point)) => TouchEvent::Pressed(point),
            (Some(_), None) => TouchEvent::Released,
            (Some(previous), Some(point)) if previous != point => TouchEvent::Moved(point),
            _ => TouchEvent::Idle,
        };
        self.previous = current;
        Ok(event)
    }
}
