use std::sync::atomic::{AtomicU64, Ordering};
use std::time::Instant;

use forge_ipc::gesture::*;
use smithay::utils::{Logical, Point, Size};

static NEXT_GESTURE_ID: AtomicU64 = AtomicU64::new(1);

pub struct GestureRecognizer
{
    pub config:       GestureConfig,
    active:           Option<ActiveGesture>,
    last_tap_time:    Option<Instant>,
    last_tap_pos:     Option<Vec2>,
    screen_size:      (f32, f32),
}

struct ActiveGesture
{
    id:            u64,
    gesture_type:  GestureType,
    origin:        Vec2,
    position:      Vec2,
    prev_position: Vec2,
    edge:          Edge,
    direction:     Direction,
    state_flags:   u8,
    axis_locked:   bool,
    start_time:    Instant,
    pointer_count: u8,
    emitted_start: bool,
}

impl GestureRecognizer
{
    pub fn new( config: GestureConfig ) -> Self
    {
        Self
        {
            config,
            active: None,
            last_tap_time: None,
            last_tap_pos: None,
            screen_size: ( 720.0, 1440.0 ),
        }
    }

    pub fn set_screen_size( &mut self, size: Size<i32, Logical> )
    {
        self.screen_size = ( size.w as f32, size.h as f32 );
    }

    pub fn on_touch_down( &mut self, pos: Point<f64, Logical>, pointer_count: u8 ) -> Option<GestureEvent>
    {
        let origin = Vec2::new( pos.x as f32, pos.y as f32 );
        let edge = self.detect_edge( &origin );
        let id = NEXT_GESTURE_ID.fetch_add( 1, Ordering::Relaxed );

        self.active = Some( ActiveGesture
        {
            id,
            gesture_type: if edge != Edge::None { GestureType::EdgePull } else { GestureType::Pan },
            origin,
            position: origin,
            prev_position: origin,
            edge,
            direction: Direction::None,
            state_flags: 0,
            axis_locked: false,
            start_time: Instant::now(),
            pointer_count,
            emitted_start: false,
        });

        None
    }

    pub fn on_touch_motion( &mut self, pos: Point<f64, Logical> ) -> Option<GestureEvent>
    {
        let active = self.active.as_mut()?;
        let new_pos = Vec2::new( pos.x as f32, pos.y as f32 );
        active.prev_position = active.position;
        active.position = new_pos;

        let dx = new_pos.x - active.origin.x;
        let dy = new_pos.y - active.origin.y;
        let dist = ( dx * dx + dy * dy ).sqrt();

        if !active.axis_locked && dist > self.config.min_swipe_distance_px * 0.5
        {
            let ratio = dx.abs() / dy.abs().max( 0.01 );
            if ratio > self.config.axis_lock_threshold
            {
                active.state_flags |= StateFlags::AXIS_LOCKED_X;
                active.axis_locked = true;
            } else if 1.0 / ratio > self.config.axis_lock_threshold {
                active.state_flags |= StateFlags::AXIS_LOCKED_Y;
                active.axis_locked = true;
            }
        }

        active.direction = Self::compute_direction( dx, dy );

        let elapsed = active.start_time.elapsed().as_millis() as u32;
        if elapsed >= self.config.hold_threshold_ms
        {
            active.state_flags |= StateFlags::HOLDING;
        }

        if dist > self.config.min_swipe_distance_px && active.gesture_type == GestureType::Pan
        {
            active.gesture_type = GestureType::Swipe;
        }

        if !active.emitted_start
        {
            if dist > 10.0
            {
                active.emitted_start = true;
                return self.build_event( GesturePhase::Start );
            }
            return None;
        }

        self.build_event( GesturePhase::Update )
    }

    pub fn on_touch_up( &mut self, pos: Point<f64, Logical> ) -> Vec<GestureEvent>
    {
        let mut events = Vec::new();
        let new_pos = Vec2::new( pos.x as f32, pos.y as f32 );

        if let Some( ref mut active ) = self.active
        {
            active.position = new_pos;

            let dx = new_pos.x - active.origin.x;
            let dy = new_pos.y - active.origin.y;
            let dist = ( dx * dx + dy * dy ).sqrt();
            let elapsed = active.start_time.elapsed().as_millis() as u32;

            let vx = active.position.x - active.prev_position.x;
            let vy = active.position.y - active.prev_position.y;
            let vel = ( vx * vx + vy * vy ).sqrt();
            if vel >= self.config.min_swipe_velocity
            {
                active.state_flags |= StateFlags::FLING;
            }

            if dist < self.config.min_swipe_distance_px * 0.5 && elapsed < self.config.hold_threshold_ms
            {
                if let ( Some( tap_time ), Some( tap_pos ) ) = ( self.last_tap_time, self.last_tap_pos )
                {
                    let tap_dt = tap_time.elapsed().as_millis();
                    let tap_dx = new_pos.x - tap_pos.x;
                    let tap_dy = new_pos.y - tap_pos.y;
                    let tap_dist = ( tap_dx * tap_dx + tap_dy * tap_dy ).sqrt();

                    if tap_dt < 300 && tap_dist < 50.0
                    {
                        active.gesture_type = GestureType::DoubleTap;
                        self.last_tap_time = None;
                        self.last_tap_pos = None;
                        events.extend( self.build_event_with_phase_type( GesturePhase::Start, GestureType::DoubleTap ) );
                        events.extend( self.build_event_with_phase_type( GesturePhase::End, GestureType::DoubleTap ) );
                        self.active = None;
                        return events;
                    }
                }

                active.gesture_type = GestureType::Tap;
                self.last_tap_time = Some( Instant::now() );
                self.last_tap_pos = Some( new_pos );
                events.extend( self.build_event_with_phase_type( GesturePhase::Start, GestureType::Tap ) );
                events.extend( self.build_event_with_phase_type( GesturePhase::End, GestureType::Tap ) );
                self.active = None;
                return events;
            }

            if dist < self.config.min_swipe_distance_px * 0.5 && elapsed >= self.config.hold_threshold_ms
            {
                active.gesture_type = GestureType::LongPress;
                events.extend( self.build_event_with_phase_type( GesturePhase::Start, GestureType::LongPress ) );
                events.extend( self.build_event_with_phase_type( GesturePhase::End, GestureType::LongPress ) );
                self.active = None;
                return events;
            }

            if active.emitted_start
            {
                events.extend( self.build_event( GesturePhase::End ) );
            }
        }

        self.active = None;
        events
    }

    fn detect_edge( &self, pos: &Vec2 ) -> Edge
    {
        let t = self.config.edge_threshold_px;
        let ( w, h ) = self.screen_size;

        if pos.y < t             { return Edge::Top; }
        if pos.y > h - t         { return Edge::Bottom; }
        if pos.x < t             { return Edge::Left; }
        if pos.x > w - t         { return Edge::Right; }
        Edge::None
    }

    fn compute_direction( dx: f32, dy: f32 ) -> Direction
    {
        if dx.abs() < 5.0 && dy.abs() < 5.0 { return Direction::None; }
        if dx.abs() > dy.abs()
        {
            if dx > 0.0 { Direction::Right } else { Direction::Left }
        } else {
            if dy > 0.0 { Direction::Down } else { Direction::Up }
        }
    }

    fn compute_progress( &self, active: &ActiveGesture ) -> f32
    {
        let ( _, h ) = self.screen_size;
        let dx = active.position.x - active.origin.x;
        let dy = active.position.y - active.origin.y;

        match active.edge
        {
            Edge::Top    => ( dy / ( h * 0.5 ) ).clamp( 0.0, 1.0 ),
            Edge::Bottom => ( -dy / ( h * 0.4 ) ).clamp( 0.0, 1.0 ),
            Edge::Left   => ( dx / ( h * 0.4 ) ).clamp( 0.0, 1.0 ),
            Edge::Right  => ( -dx / ( h * 0.4 ) ).clamp( 0.0, 1.0 ),
            Edge::None   =>
            {
                let dist = ( dx * dx + dy * dy ).sqrt();
                ( dist / ( h * 0.3 ) ).clamp( 0.0, 1.0 )
            }
        }
    }

    fn build_event( &self, phase: GesturePhase ) -> Option<GestureEvent>
    {
        let active = self.active.as_ref()?;
        Some( self.build_event_inner( phase, active.gesture_type, active ) )
    }

    fn build_event_with_phase_type( &self, phase: GesturePhase, gesture_type: GestureType ) -> Option<GestureEvent>
    {
        let active = self.active.as_ref()?;
        Some( self.build_event_inner( phase, gesture_type, active ) )
    }

    fn build_event_inner( &self, phase: GesturePhase, gesture_type: GestureType, active: &ActiveGesture ) -> GestureEvent
    {
        let dx = active.position.x - active.prev_position.x;
        let dy = active.position.y - active.prev_position.y;

        GestureEvent
        {
            gesture_id:    active.id,
            phase,
            gesture_type,
            pointer_count: active.pointer_count,
            origin:        active.origin,
            position:      active.position,
            delta:         Vec2::new( dx, dy ),
            velocity:      Vec2::new( dx, dy ),
            direction:     active.direction,
            edge:          active.edge,
            progress:      self.compute_progress( active ),
            elapsed_ms:    active.start_time.elapsed().as_millis() as u32,
            state_flags:   active.state_flags,
        }
    }
}
