extends Node

signal exchange_tracking
signal exchange_begins
signal exchange_ends
signal exchange_finished

enum journey {
        NONE,
        OUTWARD,
        EXCHANGE,
        RETURN
}

onready var turba:Node = get_node("/root/turba")
onready var bot_simple:PackedScene = load( 'res://animations/robots/bot.tscn' )

export(Resource) var bot_tmpl:Resource =                     null 

export(float) var reach_radius:float =                         0.05
export(float) var reach_dot:float =                         0.97
export(float) var near_radius:float =                         2
export(float) var max_motion_speed:float =                     1.5
export(float) var max_steer_speed:float =                     5
export(float,0,1) var motion_inertia:float =                 0.4
export(float,0,1) var steer_inertia:float =                 0.1
export(float,0,1) var motion_steer_ratio:float =             0.5
export(float,0,0.1) var noise:float =                         0.05
export(float,0,2) var motion_multiply:float =                 1
export(float,0,1800) var _swap_time:float =                 5
export(float,0,1800) var _exchange_time:float =             15

export(NodePath) var _scene_manager:NodePath =                 ''
export(NodePath) var _main_camera:NodePath =                 ''
export(float,0,1800) var min_tracking_time:float =             10
export(float,0,1800) var max_tracking_time:float =             20
export(bool) var tracking:bool =                             false setget set_tracking
export(bool) var play:bool =                                 false setget set_play

onready var scene_manager:Node =                             get_node( _scene_manager )
onready var main_camera:Camera =                             get_node( _main_camera )
onready var viewport_size:Vector2 =                         get_viewport().size

var bots:Array = []
var swap_done:bool = false
var exchange_timer:float = 0
var direction:int = journey.NONE

var request_tracking:bool = false
var tracking_enabled:bool = false
var tracking_time:float = 0
var track_camera:Camera = null
var fov_camera:Camera = null
var avoid_jump:int = 0

var request_pub = null
var request_bot = null

var is_ready:bool = false

var start_count:int = int(OS.get_unix_time()%4)
var start_changed:bool = false

func set_play( b:bool ) -> void:
        
        if turba:
                turba.write_log( self.name + " play? " + str(b) )
        
        if !is_ready:
                play = b
                return
        
        if !is_inside_tree():
                play = false
        elif play == b:
                return
        
        play = b
        
        if !play:
                
                request_pub = null
                request_bot = null
                direction = journey.NONE
                if tracking:
                        release_tracking()
                for b in bots:
                        stop_bot(b)
                
        else:
                
                direction = journey.OUTWARD
                var texture_set:Array = turba.get_flag_texture_pair()
                var bi:int = 0
                for b in bots:
                        start_bot(b)
                        var fm:Resource = turba.get_flag_exchange_mesh( bi )
                        if fm != null:
                                b.display.set_flag_mesh( fm )
                        if bi < texture_set.size():
                                b.display.set_avatar_tex( load(texture_set[bi].avatar) )
                                b.display.set_flag_tex( load(texture_set[bi].flag) )
                        bi += 1
                if tracking:
                        set_tracking( tracking, true )

func set_tracking( b:bool, force:bool = false ) -> void:
        
        if scene_manager == null or main_camera == null:
                tracking = false
        if !force and tracking == b:
                return
        
        tracking = b
        
        if !play:
                return
        
        if !tracking:
                release_tracking()
        else:
                request_tracking = true

func release_tracking() -> void:
        for b in bots:
                b.tracking = false
        tracking_enabled = false
        request_tracking = false
        scene_manager.markov_play( true )
        main_camera.track( null )

func start_bot( bdata:Dictionary ) -> void:
        
        if not start_changed:
                start_changed = true
                start_count += 1
  
        var tid:int = int(start_count%4)
        
        bdata.origin = bdata.root.get_node("origin"+str(tid))
        bdata.return = bdata.root.get_node("return"+str(tid))
        bdata.target = bdata.root.get_node("target"+str(tid))
        #print("start_bot @ start_count ", start_count, " root:", bdata.root)
        #print(bdata.origin, " >> origin ", tid, " => ", bdata.root.get_node("origin"+str(tid)))
        #print(bdata.return , " >> return ", tid, " => ", bdata.root.get_node("return"+str(tid)))
        #print(bdata.target, ">> target ", tid, " => ", bdata.root.get_node("target"+str(tid)))
        if bdata.origin== null or bdata.target == null:
                print("TOTAL FOIRADE!")
                return
                
        if bdata.return == null:
                bdata.return = bdata.origin.duplicate()
                bdata.root.add_child( bdata.return)
                bdata.return.name = "return"+str(tid)
                bdata.return.global_transform.basis = bdata.return.global_transform.basis * Basis( Vector3.UP, PI )
        
        var bot:Spatial
        if bdata.bot == null:
                bot = bot_simple.instance()
                bdata.root.add_child( bot )
                bot.connect( "robot_move", self, "robot_signal" )
                bot.connect( "robot_near", self, "robot_signal" )
                bot.connect( "robot_reach", self, "robot_signal" )
                # loading of display
                bdata.display = bot_tmpl.instance()
                bot.add_child( bdata.display )
                bdata.bot = bot
                #print( "bot LOADED", bdata.display )
        else:
                bot = bdata.bot
        
        # configuration
        bot.global_transform = bdata.origin.global_transform
        bot.load_target( bdata.target )
        bot.reach_radius = reach_radius
        bot.reach_dot = reach_dot
        bot.near_radius = near_radius
        bot.max_motion_speed = max_motion_speed
        bot.max_steer_speed = max_steer_speed
        bot.motion_inertia = motion_inertia
        bot.steer_inertia = steer_inertia
        bot.motion_steer_ratio = motion_steer_ratio
        bot.noise = noise
        
        bot.start()
        robot_signal( bot )
        # disabling tracking
        bdata.tracking = false

func stop_bot( bdata:Dictionary ) -> void:
        var bot:Spatial = bdata.bot
        if bdata.bot != null:
                bot.disconnect( "robot_move", self, "robot_signal" )
                bot.disconnect( "robot_near", self, "robot_signal" )
                bot.disconnect( "robot_reach", self, "robot_signal" )
                bdata.display = null # releasing ref to display
                bdata.root.remove_child( bot )
                bot.queue_free()
                bdata.bot = null
                bdata.status = -1

func robot_signal( robot:Node ) -> void:
        
#    0     DEFAULT
#    1     MOVE
#    2     NEAR
#    3     REACH
        
        var active_bots:int = 0
        for b in bots:
                if b.bot == robot:
                        b.status = robot.status
                match direction:
                        journey.NONE:
                                continue
                        journey.EXCHANGE:
                                continue
                        journey.OUTWARD:
                                if b.status != 3:
                                        active_bots += 1
                        journey.RETURN:
                                if b.status < 2:
                                        active_bots += 1
        
        if active_bots == 0:
                match direction:
                        journey.OUTWARD:
                                # eanbling swap
                                swap_done = false
                                # starting timer
                                exchange_timer = _exchange_time
                                direction = journey.EXCHANGE
                                for b in bots:
                                        b.display.set_exchange( true )
                                emit_signal( "exchange_begins", self )
                        journey.RETURN:
                                direction = journey.NONE
                                set_play( false )
                                emit_signal( "exchange_finished", self )

func viewport_size_changed() -> void:
         viewport_size = get_viewport().size

func pub_ready() -> void:
        
        if scene_manager == null:
                fov_camera = null
        else:
                fov_camera = scene_manager.current_pub.get_node( scene_manager.current_pub.cameras[0] )
        
        if tracking_enabled:
                
                if request_pub == scene_manager.current_pub_path:
                        main_camera.track( request_bot.bot )
                        for b in bots:
                                if b == request_bot:
                                        b.tracking = true
                                else:
                                        b.tracking = false
                
                if avoid_jump > 0:
                        avoid_jump -=1
                else:
                        main_camera.slerp_basis( 1 )
        
        request_pub = null
        request_bot = null

func _ready() -> void:
        
        is_ready = true
        set_play( play )
        
        for c in get_children():
                bots.append({
                        'root': c,
                        'origin': null,
                        'target': null,
                        'return': null,
                        'bot': null,
                        'display': null,
                        'tracking': false,
                        'candidates': [],
                        'status': -1
                })
        
        track_camera = Camera.new()
        add_child(track_camera)
        
        if scene_manager != null:
                scene_manager.connect( "pub_ready", self, "pub_ready" )
        get_viewport().connect( "size_changed", self, "viewport_size_changed" )

func seek_camera() -> bool:
        
        if scene_manager == null:
                return false
        
        # detecting cameras and computing distances
        for pub in turba.all_cameras:
                if pub == scene_manager.current_pub_path:
                        continue
                for cd in turba.all_cameras[pub].camera_data:
                        turba.apply_camera_data( track_camera, cd )
                        for b in bots:
                                if b.tracking:
                                        continue
                                if turba.inside_fov( track_camera, b.bot.translation, viewport_size, main_camera.track_near, -1 ):
                                        b.candidates.append({
                                                'distance': ( track_camera.global_transform.origin - b.bot.global_transform.origin ).length(),
                                                'pub': pub
                                        })
        
        # sorting candidates and selecting best pub
        var shortest_dist:float = -1
        request_pub = null
        request_bot = null
        for b in bots:
                if b.candidates.empty():
                        continue
                if b.candidates.size() > 1:
                        b.candidates.sort_custom( turba.TurbaSorter, "camera_distance_sort" )
                if shortest_dist == -1 or shortest_dist > b.candidates[0].distance:
                        shortest_dist = b.candidates[0].distance
                        request_bot = b
                        request_pub = b.candidates[0].pub
                # clean up of candidates
                b.candidates = []
                b.tracking = false
        # loading best pub
        if request_pub != null:
                scene_manager.markov_load( request_pub )
                return true
        return false

func _process(delta) -> void:
        
        start_changed = false
        
        if direction == journey.NONE:
                return
        
        if request_tracking:
                request_tracking = false
                scene_manager.markov_play( false )
                tracking_enabled = true
                tracking_time = 0
                avoid_jump = 2
                emit_signal( "exchange_tracking", self )
        
        # no camera change during exchange
        if direction != journey.EXCHANGE and tracking_enabled:
                if fov_camera != null:
                        for b in bots:
                                if b.tracking and !turba.inside_fov( fov_camera, b.bot.translation, viewport_size ):
                                        tracking_time = 0
                                        break
                # no camera switch if there is already a request posted
                if request_pub == null and tracking_time <= 0:
                        # computing cameras
                        var mult:float = 1
                        if !seek_camera():
                                mult = 0.5
                        tracking_time = rand_range( min_tracking_time, max_tracking_time ) * mult
                else:
                        tracking_time -= delta
        
        for b in bots:
                if b.bot == null:
                        continue
                var bot:Spatial = b.bot
                var display:Spatial = b.display
                var motion:Vector2 = Vector2( bot.right.dot(bot.delta_translation), bot.forward.dot(bot.delta_translation) ) * bot.motion_speed * motion_multiply
                display.set_walk( motion )
        
        if direction == journey.EXCHANGE:
                exchange_timer -= delta
                if !swap_done and exchange_timer <= _swap_time and bots.size() > 1:
                        var ftex0:Texture = bots[0].display.tex_flag
                        var ftex1:Texture = bots[1].display.tex_flag
                        bots[0].display.set_flag_tex( ftex1 )
                        bots[1].display.set_flag_tex( ftex0 )
                        swap_done = true
                if exchange_timer <= 0:
                        direction = journey.RETURN
                        for b in bots:
                                b.bot.load_target( b.return )
                        emit_signal( "exchange_ends", self )