#include <vector>
#include <HFO.hpp>
#include <cstdlib>
#include <math.h> 
#include <fstream>

using namespace std;
using namespace hfo;

/* Before running this program, first Start HFO server:
 ../bin/HFO --offense-npcs 2 --defense-agents 1 --defense-npcs 1 
This is a hand coded defense agent, which can play a 2v2 game againt 2 offense npcs when paired up with a goal keeper
Server Connection Options. See printouts from bin/HFO.*/

feature_set_t features = HIGH_LEVEL_FEATURE_SET;
string config_dir = "../bin/teams/base/config/formations-dt";
int port = 7000;
string server_addr = "localhost";
string team_name = "base_right";
bool goalie = false;

double kickable_dist = 1.504052352;
double open_area_up_limit_x = 0.747311440447;
double open_area_up_limit_y = 0.229619544504;
double open_area_low_limit_x = -0.352161264597;
double open_area_low_limit_y = 0.140736680776;
double tackle_limit = 1.613456553;
 

double HALF_FIELD_WIDTH = 68 ; // y coordinate -34 to 34 (-34 = bottom 34 = top)
double HALF_FIELD_LENGTH = 52.5; // x coordinate 0 to 52.5 (0 = goalline 52.5 = center)

struct action_with_params {
    action_t action;
    double param;
};

// Returns a random high-level action
action_t get_random_high_lv_action() {
  action_t action_indx = (action_t) ((rand() % 4) + REDUCE_ANGLE_TO_GOAL);
  return action_indx;
}

double get_actual_angle( double normalized_angle) {
        return normalized_angle * M_PI;
}
	


double get_dist_normalized (double ref_x, double ref_y, double src_x, double src_y) {
        return sqrt(pow(ref_x - src_x,2) + pow((HALF_FIELD_WIDTH/HALF_FIELD_LENGTH)*(ref_y - src_y),2));              
}




bool is_kickable(double ball_pos_x, double ball_pos_y, double opp_pos_x, double opp_pos_y) {
        return get_dist_normalized(ball_pos_x, ball_pos_y, opp_pos_x, opp_pos_y) < kickable_dist; //#param
}

bool is_in_open_area(double pos_x, double pos_y) {
        if (pos_x < open_area_up_limit_x ) {//&& pos_x > open_area_low_limit_x && pos_y < open_area_up_limit_y && pos_y > open_area_low_limit_y ) { //#param
                return false;
        } else {
                return true;
        }
}

action_with_params get_defense_action(const std::vector<float>& state_vec, double no_of_opponents, int numTMates) {
        int size_of_vec = 10 + 6*numTMates + 3*no_of_opponents;
        if (size_of_vec != state_vec.size()) {
                std :: cout <<"Invalid Feature Vector / Check the number of teammates/opponents provided";
                return {NOOP,0};
        }
        
        double agent_posx = state_vec[0];
        double agent_posy = state_vec[1];
        double agent_orientation = get_actual_angle(state_vec[2]);
        
        double opp1_unum = state_vec[9+6*numTMates+3];
        double opp2_unum = state_vec[9+(1*3)+6*numTMates+3];
        
        double ball_pos_x = state_vec[3];
        double ball_pos_y = state_vec[4];
        double opp1_pos_x = state_vec[9+6*numTMates+1];
        double opp1_pos_y = state_vec[9+6*numTMates+2];
        double opp2_pos_x = state_vec[9+(1*3)+6*numTMates+3];
        double opp2_pos_y = state_vec[9+(1*3)+6*numTMates+3];


        double opp1_dist_to_ball = get_dist_normalized(ball_pos_x, ball_pos_y, opp1_pos_x, opp1_pos_y);
        double opp1_dist_to_agent = get_dist_normalized(agent_posx, agent_posy, opp1_pos_x, opp1_pos_y);
        bool is_kickable_opp1 = is_kickable(ball_pos_x, ball_pos_y,opp1_pos_x, opp1_pos_y);
        bool is_in_open_area_opp1 = is_in_open_area(opp1_pos_x, opp1_pos_y);
    
        double opp2_dist_to_ball = get_dist_normalized(ball_pos_x, ball_pos_y, opp2_pos_x, opp2_pos_y);
        double opp2_dist_to_agent = get_dist_normalized(agent_posx, agent_posy, opp2_pos_x, opp2_pos_y);
        bool is_kickable_opp2 = is_kickable(ball_pos_x, ball_pos_y,opp2_pos_x, opp2_pos_y);
        bool is_in_open_area_opp2 = is_in_open_area(opp2_pos_x, opp2_pos_y);
    
        double tackle_limit_nn = tackle_limit;
        
        if (is_in_open_area(opp1_pos_x, opp1_pos_y) && is_in_open_area(opp2_pos_x, opp2_pos_y)) {
                //std:: cout << "In open Area" << "\n";
                if (is_kickable(ball_pos_x, ball_pos_y, opp1_pos_x, opp1_pos_y) && 
                        get_dist_normalized(ball_pos_x, ball_pos_y, opp1_pos_x, opp1_pos_y) < 
                        get_dist_normalized(ball_pos_x, ball_pos_y, opp2_pos_x, opp2_pos_y)) {
                        return {MARK_PLAYER, opp2_unum};
                        //                      return {REDUCE_ANGLE_TO_GOAL, 1};

                } else if (is_kickable(ball_pos_x, ball_pos_y, opp2_pos_x, opp2_pos_y)) {
                        return {MARK_PLAYER, opp1_unum};
                        //                      return {REDUCE_ANGLE_TO_GOAL, 1};

                } else if (get_dist_normalized(ball_pos_x, ball_pos_y, opp1_pos_x, opp1_pos_y) > 
                                   get_dist_normalized(ball_pos_x, ball_pos_y, agent_posx, agent_posy) && 
                               get_dist_normalized(ball_pos_x, ball_pos_y, opp2_pos_x, opp2_pos_y) > 
                               get_dist_normalized(ball_pos_x, ball_pos_y, agent_posx, agent_posy)) {
                        return {GO_TO_BALL,1};
                } else {
                        return {REDUCE_ANGLE_TO_GOAL, 1};
                }
        } else {
                //std:: cout << "In Penalty Area" << "\n";
                if (! is_kickable(ball_pos_x,ball_pos_y,opp1_pos_x, opp1_pos_y) && ! is_kickable(ball_pos_x,ball_pos_y,opp2_pos_x,opp2_pos_y)) 
                {
                        //std :: cout <<"IN AREA BUT GOTO\n";
                         if (get_dist_normalized(ball_pos_x, ball_pos_y, opp1_pos_x, opp1_pos_y) > get_dist_normalized(ball_pos_x, ball_pos_y, agent_posx, agent_posy) && 
                            get_dist_normalized(ball_pos_x, ball_pos_y, opp1_pos_x, opp1_pos_y) > get_dist_normalized(ball_pos_x, ball_pos_y, agent_posx, agent_posy)) {
                                        return {GO_TO_BALL,2};
                        } else {
                                return {REDUCE_ANGLE_TO_GOAL, 0};
                        }

                } else if ( get_dist_normalized (agent_posx, agent_posy, opp1_pos_x, opp1_pos_y) < tackle_limit
                           || get_dist_normalized (agent_posx, agent_posy, opp2_pos_x, opp2_pos_y) < tackle_limit ) { //#param
                        /*double turn_angle;
                        //REVISIT TURN ANGLE.. CONSIDER ACTUAL DIRECTION ALSO..
                        if (get_dist_normalized(ball_pos_x, ball_pos_y, opp1_pos_x, opp1_pos_y) < get_dist_normalized(ball_pos_x, ball_pos_y, opp1_pos_x, opp1_pos_y)) {
                                turn_angle = atan2((HALF_FIELD_WIDTH/HALF_FIELD_LENGTH)*(opp1_pos_y), opp1_pos_x) - (agent_orientation*M_PI);
                        } else {
                                turn_angle = atan2((HALF_FIELD_WIDTH/HALF_FIELD_LENGTH)*(opp2_pos_y), opp2_pos_x) - (agent_orientation*M_PI);
                        }
                        turn_angle = atan2(tan(turn_angle),1);
                        if (turn_angle > M_PI ) {
                                turn_angle -= 2*M_PI;
                        } else if (turn_angle < -M_PI) {
                                turn_angle += 2*M_PI;
                        }*/
                        /*string s = "TACKLE " + std ::to_string (turn_angle) + "\n";
                        cout << s; */
                        //return {DASH, turn_angle*180/M_PI}; 
                        //return {TACKLE, turn_angle*180/M_PI}; //TACKLE needs power od dir
                        return {MOVE,0};
                } else if ((!is_in_open_area(opp1_pos_x, opp1_pos_y) && is_in_open_area(opp2_pos_x, opp2_pos_y)) || 
                                  (!is_in_open_area(opp2_pos_x, opp2_pos_y) && is_in_open_area(opp1_pos_x, opp1_pos_y))) {
                                return {REDUCE_ANGLE_TO_GOAL,0};
                } else if (!is_in_open_area(opp1_pos_x, opp1_pos_y) && !is_in_open_area(opp2_pos_x, opp2_pos_y)) {
                        if (get_dist_normalized(ball_pos_x, ball_pos_y, opp1_pos_x, opp1_pos_y) < get_dist_normalized(ball_pos_x, ball_pos_y, opp1_pos_x, opp1_pos_y)) {
                                return {MARK_PLAYER,opp2_unum};
                        } else {
                                return {MARK_PLAYER,opp1_unum};
                        }
                } else {
                        std :: cout <<"Unknown Condition";
                        return {NOOP,0};
                }
        }
        
}
void read_params() {
    std::ifstream fin("params.txt");
    double d[6];
    int i = 0;
    while (true) {
        fin >> d[i];
        if( fin.eof() ) break;
        std::cout << d[i] << std::endl;
        i++;
                //if (i >=6 ) {
                //      std::cout << "invalid params" << d[5];
                //      exit(0);
                //}
    }
    fin.close();
    kickable_dist = (d[0]+1)* 0.818175061;
    open_area_up_limit_x = d[1];
    open_area_up_limit_y = d[2];
    open_area_low_limit_x = d[3];
    open_area_low_limit_y = d[4];
    tackle_limit = (d[5]+1)* 0.818175061;
    std :: cout << "kickable dist " << kickable_dist << "tackle_limit" <<tackle_limit;
    return;
}

void write_cost(double cost) {
    std::ofstream myfile ("cost.txt");
    if (myfile.is_open()) {
        myfile  << cost;
        myfile.close();
    }
    return;
}

int main(int argc, char** argv) {
  // Create the HFO environment
  //read_params();
  HFOEnvironment hfo;
  int random = 0;
  double numGoals = 0;
  int numEpisodes = 5000;
  double actualNumEpisodes = 0;
  // Connect to the server and request high-level feature set. See
  // manual for more information on feature sets.
  hfo.connectToServer(features, config_dir, port, server_addr,
                           team_name, goalie);
  for (int episode=0; episode<numEpisodes; episode++) {
    status_t status = IN_GAME;
    while (status == IN_GAME) {

    // Get the vector of state features for the current state
      const vector<float>& feature_vec = hfo.getState();
      if (random == 0) {
              action_with_params a = get_defense_action(feature_vec, 2, hfo.getNumTeammates());
         // std::cout << a.action << a.param;
         if (a.action == hfo :: MARK_PLAYER || a.action == hfo::TACKLE) {
                  hfo.act(a.action, a.param);
              } else if (a.action == hfo :: DASH) {
                          double power = 100;
                          hfo.act(a.action, power, a.param);
                  } else {
                      hfo.act(a.action);
              }
              string s = hfo::ActionToString(a.action) + " " +to_string(a.param) + "\n";
             // std::cout << s;
      } else {
	std::cout <<"Random";
	action_t a = get_random_high_lv_action();
	if (a == hfo :: MARK_PLAYER) {
	  hfo.act(NOOP); // why not MOVE?
	} else {
	  hfo.act(a);
	}
      }
      //hfo.act(hfo::INTERCEPT);
      status = hfo.step();
    }
    if (status==GOAL)
            numGoals = numGoals+1;
    // Check what the outcome of the episode was
    cout << "Episode " << episode << " ended with status: "
         << StatusToString(status) << std::endl;
    
    if (status==SERVER_DOWN) {
      break;
    } else {
      actualNumEpisodes++;
    }
  }
  double cost = numGoals/actualNumEpisodes;
  hfo.act(QUIT);
  //write_cost(cost);
};