
using Godot;
using System.Collections.Generic;
using System;
namespace Rokojori
{  
  [Tool]
  [GlobalClass,Icon("res://addons/rokojori_action_library/Icons/SensorManager.svg")]
  public partial class SensorManager: Node
  {
    [Export]
    public bool initializeOnReady = true;

    [Export]
    public Sensor[] sensors = [];

    [Export]
    public SensorGroup[] sensorGroups = [];

    [Export]
    public bool processSensors = true;

    [Export]
    public Node[] autoScanForSensors = [];

    [Export]
    public bool autoScanParent = true;

    [Export]
    public bool separateMouseAndKeyboardTracking = false; 

    [Export]
    public Action onActiveDeviceChange;
    public readonly EventSlot<Null> _onActiveDeviceChange = new EventSlot<Null>();
    
    [ExportGroup("Read Only")]
    [Export]
    public SensorDevice[] deviceList =[];

    [Export]
    public float[] deviceLastInputTimeStamp = [];

    [ExportGroup("Testing")]
    
    [Export]
    public SensorDevice testingLastActiveDevice;

    [Export]
    public bool showRegistratedSensors = true;

    [Export]
    public Sensor[] registratedSensors = [];

  

    
    List<SensorRunner> runners = new List<SensorRunner>();
    List<iOnInputSensor> inputers = new List<iOnInputSensor>();
    Dictionary<Sensor,SensorRunner> sensorToRunner = new Dictionary<Sensor, SensorRunner>();


    KeyboardDevice keyboardDevice = new KeyboardDevice();
    MouseDevice mouseDevice = new MouseDevice();
    GamePadDevice gamePadDevice = new GamePadDevice(); 
    MultiSensorDevice mouseKeyboardDevice = new MultiSensorDevice();

    DateTime _startTime;


    public bool isMouseKeyboardLastActive => lastActiveDevice == mouseKeyboardDevice || 
                                             lastActiveDevice == mouseDevice || 
                                             lastActiveDevice == keyboardDevice;

    public bool isControllerLastActive => lastActiveDevice == gamePadDevice; 

    public SensorDevice lastActiveDevice 
    {
      get
      {      
        if ( testingLastActiveDevice != null )
        {
          return testingLastActiveDevice;
        }
        
        var highest = Lists.IndexOfHighestValue( Lists.From( deviceLastInputTimeStamp ), t => t );

        if ( highest == -1 )
        {
          return null;
        }

        return deviceList[ highest ];
      }
     
    }

    void UpdateDevice( SensorDevice d )
    {
      var lastActive = lastActiveDevice;

      var index = Arrays.IndexOf( deviceList, d );

      if ( index == -1 )
      {
        deviceList = Arrays.Add( deviceList, d );
        index = deviceList.Length - 1;
        deviceLastInputTimeStamp = Arrays.Add( deviceLastInputTimeStamp, 0 );
      }

      deviceLastInputTimeStamp[ index ] = (float) (  DateTime.Now - _startTime ).TotalSeconds;

      if ( lastActive == lastActiveDevice )
      {
        return;
      }

      Action.Trigger( onActiveDeviceChange );
      _onActiveDeviceChange.DispatchEvent( null );
    
    }

    public void UpdateLastActiveDevice( Sensor sensor, int index )
    {      
      if ( keyboardDevice.ContainsSensor( sensor ) )
      {
        UpdateDevice( separateMouseAndKeyboardTracking ? keyboardDevice : mouseKeyboardDevice );
        
        return;
      }

      if ( mouseDevice.ContainsSensor( sensor ) )
      {
        UpdateDevice( separateMouseAndKeyboardTracking ? mouseDevice : mouseKeyboardDevice );
        return;
      }
      

      

      if ( gamePadDevice.ContainsSensor( sensor ) )
      {
        var device = Arrays.Find( deviceList, d => d is GamePadDevice gpd && gpd.deviceIndex == index );

        if ( device == null )
        {
          var gpd = new GamePadDevice();
          gpd.deviceIndex = index;
          gpd.deviceName = Input.GetJoyName( index );

          device = gpd;
        }

        UpdateDevice( device ); 
      }
    }
    
    public override void _Ready()
    {
      _startTime = DateTime.Now;
      mouseKeyboardDevice.devices = new SensorDevice[]{ mouseDevice, keyboardDevice };

      if ( ! initializeOnReady )
      {
        return;
      }

      CreateRunners();
    }

    public override void _Input( InputEvent ev )
    {
      inputers.ForEach(
        ( inp )=>
        {
          inp._Input( ev );
        }
      );
    }

    public override void _Process( double delta )
    {
      if ( Engine.IsEditorHint() || ! processSensors )
      {
        return;
      }

      runners.ForEach( r => r.Update( (float) delta ) );
    }


    public void Register( Sensor s, SensorInputHandler sih )
    {
      this.LogInfo( "Registrating", s );

      var sensorRunner = sensorToRunner[ s ];

      if ( sensorRunner.listeners.Contains( sih ) )
      {
        return;
      }
      
      sensorRunner.listeners.Add( sih );

     
    }

    public void Unregister( Sensor s, SensorInputHandler sih )
    {
      var sensorRunner = sensorToRunner[ s ];
      sensorRunner.listeners.Remove( sih );
    }

    public static void Register( SensorInputHandler handler, params Sensor[] sensors )
    {
      var sm = Unique<SensorManager>.Get();

      if ( sm == null )
      {
        Nodes.ForEachInScene<Node>( n => n.LogInfo() );
      }
      
      foreach ( var s in sensors )
      {
        if ( s == null )
        {
          continue;
        }

        ( handler as Node ).LogInfo( "Registrating", HierarchyName.Of( (Node)handler) );
        sm.Register( s, handler );
      }
    }

    public static void Unregister( SensorInputHandler handler, params Sensor[]  sensors )
    {
      var sm = Unique<SensorManager>.Get();

      foreach ( var s in sensors )
      {
        if ( s == null )
        {
          continue;
        }

        ( handler as Node ).LogInfo( "Unregistrating" );

        sm.Unregister( s, handler );
      }
    }
    
    HashSet<Sensor> sensorsSet = new HashSet<Sensor>();

    void AddSensor( Sensor s )
    {
      if ( s == null || sensorsSet.Contains( s ) )
      {
        return;
      }

      // this.LogInfo( "Including:", HierarchyName.Of( s ) );

      AddSensorsFrom( s );

      sensorsSet.Add( s );
      runners.Add( new SensorRunner( s ) );

      if ( showRegistratedSensors )
      {
        registratedSensors = Arrays.Add( registratedSensors, s ); 
      }
    }

    HashSet<object> _scannedObjects = new HashSet<object>();

    void AddSensorsFrom( object obj )
    { 
      if ( obj == null || _scannedObjects.Contains( obj ) ) 
      {
        return;
      }

      _scannedObjects.Add( obj ); 


      var sensors = ReflectionHelper.GetDataMemberValues<Sensor>( obj );

      if ( sensors.Count > 0 )
      {
        if ( obj is Node n )
        {
          n.LogInfo( sensors );
        }
        else if ( obj is Resource r )
        {
          r.LogInfo( sensors );
        }
      }
      

      sensors.ForEach( 
        s => 
        {
          AddSensor( s );
        }
      );

      var sensorArrays = ReflectionHelper.GetDataMemberValues<Sensor[]>( obj );

      sensorArrays.ForEach( 
        s => 
        {
          for ( int i = 0; i < s.Length; i++ )
          {
            AddSensor( s[ i ] );
          }
        }
      );

      var resources = ReflectionHelper.GetDataMemberValues<Resource>( obj );

      resources.ForEach( r => AddSensorsFrom( r ) );
    }

    void CreateRunners()
    { 
      if ( sensors == null )
      {
        sensors = new Sensor[]{};
      }

      if ( sensorGroups == null )
      {
        sensorGroups = new SensorGroup[]{};
      }      

      foreach ( var s in sensors )
      {
        AddSensor( s );
      }

      foreach ( var g in sensorGroups )
      {
        foreach ( var s in g.sensors )
        {
          AddSensor( s );
        }
      }


      foreach ( var n in autoScanForSensors )
      {
        Nodes.ForEach<Node>( n, AddSensorsFrom ); 
      }

      if ( autoScanParent )
      {
        Nodes.ForEach<Node>( GetParent(), AddSensorsFrom ); 
      }

      runners.ForEach( 
        r =>       
        { 
          sensorToRunner[ r.sensor ] = r;

          if ( r.sensor is iOnInputSensor oi )
          {
            inputers.Add( oi );
          }
        }
      );

    
      // this.LogInfo( "Created runners:", runners.Count );
    }
  }
}