/*
This is an excerpt from the code for the device for the Wickens and Martin-Emerson task.
The emphasis is on how the device interacts with the EPIC system, so the many functions
that handle various details are not shown in this sample.

A run consists of a series of blocks, with the specified number of trials in each block.
Each block begins with a period of time in which the tracking cursor does not move.
*/

#include "Wickens_device.h"
#include "Geometry.h"
#include "Coordinator.h"
#include "Output_tee.h"
#include "Epic_globals.h"
#include "Human_processor.h"
#include "Numeric_utilities.h"
#include "Symbol_utilities.h"
#include "Assert.h"
#include "Device_exception.h"
#include "Epic_standard_names.h" // defines standard constants, like "Color" as Color_c

#include <iostream>
#include <iomanip>
#include <fstream>
#include <string>
#include <vector>
#include <algorithm>
using namespace std;

/* Symbol constants would normally be used, but 
literal strings are shown in this sample excerpt for clarity. E.g.:
const Symbol Start_experiment_c("Start_experiment");
*/

// experiment constants
const long choice_task_delay_minimum_c = 5000;
const long choice_task_delay_range_c = 2000;
const long error_sampling_interval_c = 125;
const long error_sampling_duration_c = 2000;
const long cursor_update_interval_c = 20;

// const int n_separations = 12;
// const double separations[n_separations] = {0., 3.2, 6.4, 9.6, 12.8, 16., 19.2, 22.4, 25.6, 28.8, 32., 35.2};
const int n_separations = 5;
const double separations[n_separations] = {3.2, 6.4, 12.8, 22.4, 35.2};

// display constants
// tracking region origin is at 0, +10
const GU::Point tracking_target_location_c(0., 10.);
const double degrees_per_pixel_c = .0584795;
const double pixels_per_degree_c = 1. / degrees_per_pixel_c;
// scaled rms error is rms error in VA units * pixels per degree-va


// B1 response is left middle for left-arrow
// B2 response is left index for right-arrow


// define the global function that creates a specific device
Device_processor * create_specific_device()
{
	return new Wickens_device("Wickens device", Normal_out);
}

// The constructor simply saves the experiment condition specifications away.
Wickens_device::Wickens_device(const string& id, Output_tee& ot) :
	Device_processor(id, ot), new_control_input(false), 
	stimulus_number(0), 
	condition_string("10 -1 Easy"),
	n_trials(0), stimulus_separation(0.),tracking_difficulty("Easy"),
	trial(0),
	sampling_error(false), cursor_movement_enabled(false)
{	
	parse_condition_string();	// set up the conditions
}

void Wickens_device::initialize()
{
	// initialize the base object
	Device_processor::initialize();
	separation_index = 0;
	tracking_index = 0;
	initialize_block();
	device_out << "Conditions: " << condition_string << endl;
	// schedule the start event - provide some delay before putting up objects
	do_device_delay_event(200, "Start_experiment", nil_c);
	// schedule the start event for the first block
	do_device_delay_event(1000, "Start_block", nil_c);

}

// all events for the device first come here, then to the specific handling function.
void Wickens_device::accept_event(Smart_Pointer<const Device_event> event_ptr)
{
	if(get_trace() && Trace_out)
		Trace_out << processor_info() << typeid(*event_ptr).name() << " received" << endl;
	// tell the message to handle itself with yourself!
	event_ptr->handle_self(this);
}

// handle device delay events
void Wickens_device::handle_event(Smart_Pointer<const Device_Delay_event> msg_ptr)
{
	if(msg_ptr->type == "Start_experiment") {
		start_experiment();
		}
	else if(msg_ptr->type == "Start_block") {
		start_block();
		}
	if(msg_ptr->type == "Move_cursor") {
		update_cursor_position();
		}
	else if(msg_ptr->type == "Start_choice_trial") {
		start_choice_trial();
		}
	else if(msg_ptr->type == "Sample_error") {
		sample_error();
		}
	else if(msg_ptr->type == "Stop_error_sampling") {
		stop_error_sampling();
		}
	else if(msg_ptr->type == "Stop") {
		Coordinator::get_instance().stop();
		}
	else
		throw Device_exception(this, string(" Unknown delay type: ") + (msg_ptr->type).str());
}

/*
Start the experiment by presenting the objects
	tracking origin - a one-degree circle
	tracking cursor - a cross-hairs
	choice stimulus circle - a 2 degree circle possibly displaced from tracking origin
*/
void Wickens_device::start_experiment()
{
	target_location = tracking_target_location_c;
	cursor_location = tracking_target_location_c;
	human_ptr->make_visual_object_appear("Tracking_target", target_location, GU::Size(1., 1.));
	human_ptr->set_visual_object_property("Tracking_target", "Color", Black_c);
	human_ptr->set_visual_object_property("Tracking_target", Shape_c, Empty_Rectangle_c);
	
	human_ptr->make_visual_object_appear("Tracking_cursor", cursor_location, GU::Size(1., 1.));
	human_ptr->set_visual_object_property("Tracking_cursor", "Color", Red_c);
	human_ptr->set_visual_object_property("Tracking_cursor", Shape_c, Cross_Hairs_c);
	
	human_ptr->make_visual_object_appear("Stimulus_circle", stimulus_location, GU::Size(2., 2.));
	human_ptr->set_visual_object_property("Stimulus_circle", "Color", Black_c);
	human_ptr->set_visual_object_property("Stimulus_circle", Shape_c, Empty_Circle_c);
}


// Start the block by positioning the choice circle and 
// starting to move the cursor and presenting the stimulus
void Wickens_device::start_block()
{
	// reposition the objects
	target_location = tracking_target_location_c;
	cursor_location = tracking_target_location_c;
	human_ptr->set_visual_object_location("Tracking_target", target_location);
	human_ptr->set_visual_object_location("Tracking_cursor", cursor_location);
	human_ptr->set_visual_object_location("Stimulus_circle", stimulus_location);
	
	cursor_movement_enabled = true;
	do_device_delay_event(1000, "Move_cursor", nil_c);
	do_device_delay_event(2000, "Start_choice_trial", nil_c);	
}

// This function updates the cursor position using the last control input
void Wickens_device::update_cursor_position()
{
	/* not shown nere: calculate the new cursor location */
	cursor_location  = new_cursor_location;

	human_ptr->set_visual_object_location("Tracking_cursor", cursor_location);
	// compute and update the tracking error as a property of the tracking cursor
	Symbol distance(cartesian_distance(cursor_location, target_location));
	human_ptr->set_visual_object_property("Tracking_cursor", Distance_c, distance);
	
	// schedule a new update
	do_device_delay_event(cursor_update_interval_c, "Move_cursor", nil_c);
}

// the joystick contains a location which is not the new location of the cursor,
// but rather its vector is used to compute the displacement to be applied to the cursor
void Wickens_device::handle_event(Smart_Pointer<const Device_Ply_event> event_ptr)
{	
	// the control input movement vector is saved to be combined with the forcing function
	control_input = GU::Cartesian_vector(event_ptr->movement_vector);
	new_control_input = true;

	if(get_trace() && Trace_out) {
		if(event_ptr->target_name != nil_c)
			Trace_out << processor_info() << "Final Ply input to " << event_ptr->target_name << endl;
		}
}

void Wickens_device::start_choice_trial()
{
	response_made = false;
	stimulus_name = concatenate_to_Symbol("ChoiceStimulus", ++stimulus_number);
	// flipped a coin to choose a stimulus
	choice_stimulus = static_cast<Choice_stimuli_t>(random_int(2));
	choice_stimulus_onset = get_time();
	human_ptr->make_visual_object_appear(stimulus_name, stimulus_location, GU::Size(1.5, 1.));
	human_ptr->set_visual_object_property(stimulus_name, Color_c, Black_c);
	if(choice_stimulus == LEFT_ARROW)
		human_ptr->set_visual_object_property(stimulus_name, Shape_c, Left_Arrow_c);
	else
		human_ptr->set_visual_object_property(stimulus_name, Shape_c, Right_Arrow_c);
	
	// increment here in case response is never made
	trial++;
	// start sampling the tracking error
	sampling_error = true;
	sample_error();
	// schedule a sampling cessation
	do_device_delay_event(error_sampling_duration_c, "Stop_error_sampling", nil_c);	
	
}

void Wickens_device::handle_event(Smart_Pointer<const Device_Keystroke_event> msg_ptr)
{
	if(msg_ptr->key_name == "B1" && choice_stimulus == LEFT_ARROW ||
		msg_ptr->key_name == "B2" && choice_stimulus == RIGHT_ARROW) {
		// a correct response - accumulate it
		long rt = get_time() - choice_stimulus_onset;
		current_rt.update(rt);
		if(get_trace() && Trace_out)
			Trace_out << processor_info() << "Trial " << trial << ": RT= " << rt << endl;
		}
	else
		throw Device_exception(this, string("Unrecognized keystroke: ") + msg_ptr->key_name.str());
		
	response_made = true;
	// remove the stimulus
	human_ptr->make_visual_object_disappear(stimulus_name);
}


