
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 bool autoScan = true;

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

    [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 = [];

    [Export]
    public bool confineMouse = false;

  

    
    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 ( Engine.IsEditorHint() )
        {
          return null;
        } 

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

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

        return deviceList[ highest ];
      }
     
    }

    bool hasDevice = false;

    void UpdateDevice( SensorDevice d )
    {
      if ( Engine.IsEditorHint() )
      {
        return;
      }

      // this.LogInfo( 
      //   "UpdateDevice:", d.GetType().Name, 
      //   "Is Mouse:", DefaultSensorDeviceSelector.IsMouseDevice( d ),
      //   "Last:", lastActiveDevice == null ? "null" : lastActiveDevice.GetType().Name
      //   );

      var lastActive = lastActiveDevice;
      

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

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

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

      if ( hasDevice && lastActive == lastActiveDevice )
      {
        return;
      }

      hasDevice = true;

      this.LogInfo( "Device Type Change:", lastActiveDevice.GetInfo() );

      if ( confineMouse && DefaultSensorDeviceSelector.IsMouseDevice( lastActiveDevice ) )
      {
        Input.MouseMode = Input.MouseModeEnum.Captured;
        
      }

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

    public void UpdateLastActiveDevice( Sensor sensor, int index )
    {      
      if ( Engine.IsEditorHint() )
      {
        return;
      }

      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()
    // {
    //   if ( Engine.IsEditorHint() )
    //   {
    //     return;
    //   }

    //   //this.LogInfo( "" );
    //   _startTime = DateTime.Now;
    //   mouseKeyboardDevice.devices = new SensorDevice[]{ mouseDevice, keyboardDevice };

    //   if ( ! initializeOnReady )
    //   {
    //     return;
    //   }

    //   CreateRunners();
    // }

    public void Initialize()
    {
      _startTime = DateTime.Now;
      mouseKeyboardDevice.devices = new SensorDevice[]{ mouseDevice, keyboardDevice };
      CreateRunners();
    }

    public override void _Input( InputEvent ev )
    {
      if ( Engine.IsEditorHint() )
      {
        return;
      }

      //this.LogInfo( "" );
      inputers.ForEach(
        ( inp )=>
        {
          inp._Input( ev );
        }
      );
    }

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

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


    public void Register( Sensor s, SensorInputHandler sih )
    {
      if ( Engine.IsEditorHint() )
      {
        return;
      }
      //this.LogInfo( "Register", s );

      if ( ! sensorToRunner.ContainsKey( s ) )
      {
        return;
      }

      var sensorRunner = sensorToRunner[ s ];

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

     
    }

    public void Unregister( Sensor s, SensorInputHandler sih )
    {
      if ( Engine.IsEditorHint() )
      {
        return;
      }

      //this.LogInfo( "Unregister", s );
      var sensorRunner = sensorToRunner[ s ];
      sensorRunner.listeners.Remove( sih );
    }

    public static void Register( SensorInputHandler handler, params Sensor[] sensors )
    {
      if ( Engine.IsEditorHint() )
      {
        return;
      }

      //RJLog.Log( "Register", 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 )
    {
      if ( Engine.IsEditorHint() )
      {
        return;
      }


      //RJLog.Log( "Unregister", 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 ( Engine.IsEditorHint() )
      {
        return;
      }
      
      //this.LogInfo( "AddSensor", 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.AddEntry( registratedSensors, s ); 
      }
    }

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

    
    bool IsIgnoreType( object obj )
    {
      var ignoreTypes = new List<Type>()
      {
        typeof( GpuParticles3D ),
        typeof( MeshInstance3D ),
        typeof( Node3D ),
        typeof( CollisionShape3D ),
        typeof( OmniLight3D ),
        typeof( DirectionalLight3D ),
        typeof( WorldEnvironment ),
        typeof( Area3D ),
        typeof( CharacterBody3D ),
        typeof( AudioStreamPlayer3D ),
        typeof( ReflectionProbe ),
        typeof( SensorManager ),
        typeof( SensorManagerSetup )
      }; 


      return ignoreTypes.IndexOf( obj.GetType() ) != -1;
    }

    void AddSensorsFrom( object obj )
    { 
      if ( Engine.IsEditorHint() )
      {
        return;
      }

      //this.LogInfo( "AddSensorsFrom", obj );
      
      try
      {

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

        
        _scannedObjects.Add( obj ); 

        if ( IsIgnoreType( obj ) )
        {
          return;
        }



        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 ) );

      }
      catch( System.Exception e )
      {
        this.LogError( e );
      }
    }

    void CreateRunners()
    { 
      if ( Engine.IsEditorHint() )
      {
        return;
      }

      //this.LogInfo( "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, n => AddSensorsFrom( n ) ); 
      }

      if ( autoScan )
      {
        this.ForEachInRoot<Node>(
          ( n )=>
          {
            if ( IsIgnoreType( n ) )
            {
              return;
            }

            AddSensorsFrom( n );
          }
        );
      }
      // 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 );
    }
  }
}