/* Implementation of the lobotomized "iplayer" AI in Xconq.
   Copyright (C) 1987-1989, 1991-2000 Stanley T. Shebs.

Xconq is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2, or (at your option)
any later version.  See the file COPYING.  */

#include "conq.h"
#include "kpublic.h"
#include "ai.h"

extern void register_iplayer(AI_ops *ops);

/* Local function declarations. */

static void iplayer_init(Side *side);
static void iplayer_init_turn(Side *side);
static void iplayer_create_strategy(Side *side);
static void iplayer_review_units(Side *side);
static void iplayer_decide_plan(Side *side, Unit *unit);
static int iplayer_adjust_plan(Side *side, Unit *unit);
static void iplayer_react_to_task_result(Side *side, Unit *unit, Task *task, TaskOutcome rslt);
static void iplayer_react_to_new_side(Side *side, Side *side2);
static void iplayer_finish_movement(Side *side);
static void iplayer_rethink_plan(Unit *unit);

void
register_iplayer(AI_ops *ops)
{
    ops->name = "iplayer";
    ops->help = "AI for independent units only";
    ops->to_init = iplayer_init;
    ops->to_init_turn = iplayer_init_turn;
    ops->to_decide_plan = iplayer_decide_plan;
    ops->to_react_to_task_result = iplayer_react_to_task_result;
    ops->to_react_to_new_side = iplayer_react_to_new_side;
    ops->to_adjust_plan = iplayer_adjust_plan;
    ops->to_finish_movement = iplayer_finish_movement;
}

static void
iplayer_init(Side *side)
{
    Unit *unit;

    /* Delete any old strategy object in case we just switched AI type. */
    if (side->ai != NULL) {
    	free(side->ai);
    	side->ai = NULL;
    }    
    iplayer_create_strategy(side);
    /* If the side has no units at the moment, it doesn't really need to
       plan. */
    if (!side_has_units(side))
      return;
    /* Reset plans of any units that were not doing anything. */
    for_all_side_units(side, unit) {
    	if (in_play(unit) && ai_controlled(unit)) {
	    net_force_replan(side, unit, TRUE);
    	}
    }
}

/* At the beginning of each turn, make plans and review the situation. */

static void
iplayer_init_turn(Side *side)
{
    /* Cases where we no longer need to run. */
    if (!side->ingame)
      return;
    /* A side without units hasn't got anything to do but wait. */
    /* (should account for possible units on controlled sides) */
    if (!side_has_units(side))
      return;
    /* Iplayers in a hacked game will not play, unless they're being
       debugged. */
    if (compromised && !DebugM)
      return;
    update_all_progress_displays("ai turn init start", side->id);
    DMprintf("%s iplayer init turn\n", side_desig(side));
    /* Check out all of our units. */
    iplayer_review_units(side);
    /* (should be integrated better) */
    iplayer_finish_movement(side);
    /* Update unit plans. */
    update_unit_plans(side);
    update_all_progress_displays("", side->id);
    DMprintf("%s iplayer init turn done\n", side_desig(side));
}

/* Create and install an entirely new strategy object for the side. */

static void
iplayer_create_strategy(Side *side)
{
    Strategy *strategy = (Strategy *) xmalloc(sizeof(Strategy));

    /* Put the specific structure into a generic slot. */
    side->ai = (struct a_ai *) strategy;
}

/* Go through all our units (and allied ones?). */

static void
iplayer_review_units(Side *side)
{
    int u;
    int numoffensive[MAXUTYPES], numdefensive[MAXUTYPES];
    Unit *unit;
    Plan *plan;

    for_all_unit_types(u) {
	numoffensive[u] = numdefensive[u] = 0;
    }
    for_all_side_units(side, unit) {
	if (in_play(unit) && ai_controlled(unit)) {
	    /* Count plan types. */
	    switch (unit->plan->type) {
	      case PLAN_OFFENSIVE:
	      case PLAN_EXPLORATORY:
		++numoffensive[unit->type];
		break;
	      case PLAN_DEFENSIVE:
		++numdefensive[unit->type];
		break;
	      default:
	        break;
	    }
	}
    }

    for_all_side_units(side, unit) {
	if (in_play(unit) && ai_controlled(unit)) {
	    plan = unit->plan;
	    /* Goal might have become satisfied. */
	    if (plan->maingoal) {
		if (goal_truth(side, plan->maingoal) == 100) {
		    DMprintf("%s %s satisfied, removing\n",
			     unit_desig(unit), goal_desig(plan->maingoal));
		    net_force_replan(side, unit, FALSE);
		}
	    }
	    /* Don't let defense-only units pile up. */
	    if (plan->type == PLAN_DEFENSIVE
		&& mobile(unit->type)
		&& (numoffensive[unit->type] / 3) < numdefensive[unit->type]
		/* However, don't mess with units that have specific
                   defensive goals. */
		&& (plan->maingoal == NULL
			|| (plan->maingoal->type != GOAL_UNIT_OCCUPIED
			 && plan->maingoal->type != GOAL_CELL_OCCUPIED))
		&& flip_coin()) {
		DMprintf("%s one of too many on defense (%d off, %d def), replanning\n",
			 unit_desig(unit), numoffensive[unit->type], numdefensive[unit->type]);
		net_force_replan(side, unit, FALSE);
	    }
	}
    }
}

/* This is for when a unit needs a plan and asks its side for one. */

static void
iplayer_decide_plan(Side *side, Unit *unit)
{
    Plan *plan = unit->plan;
    int u = unit->type;

    switch (plan->type) {
      case PLAN_PASSIVE:
      case PLAN_NONE:
	if (mobile(u)) {
	    if (u_colonizer_worth(u) > 0) {
		assign_to_colonize(side, unit);
		return;
	    }
	    /* Assign most units to offense, save some for defense. */
	    if (u_offensive_worth(u) > 0
		&& probability(75)) {
		  assign_to_offense(side, unit);
	    } else if (u_defensive_worth(u) > 0) {
		  assign_to_defense(side, unit);
	    /* In the unlikely case that mobile units can build anything. */
	    } else if (can_build_attackers(side, u)) {
		assign_to_offense_support(side, unit);
	    } else if (can_build_defenders(side, u)) {
		assign_to_defense_support(side, unit);
	    } else {
	    }
	} else {
	    /* Unit doesn't move. */
	    if (can_build_colonizers(side, u)
		/* (should fine-tune this test) */
		&& probability(60)) {
		assign_to_colonization_support(side, unit);
	    } else if (can_build_attackers(side, u)) {
	    	assign_to_offense_support(side, unit);
	    } else if (can_build_defenders(side, u)) {
		assign_to_defense_support(side, unit);
	    } else if (u_defensive_worth(u) > 0) {
		assign_to_defense(side, unit);
	    }
	}
	break;
      case PLAN_OFFENSIVE:
	/* leave plan alone */
	break;
      case PLAN_COLONIZING:
	/* leave plan alone */
	break;
      case PLAN_IMPROVING:
	/* leave plan alone */
	break;
      case PLAN_EXPLORATORY:
	/* leave plan alone */
	break;
      case PLAN_DEFENSIVE:
	/* leave plan alone */
	break;
      default:
	break;
    }
}

static int
iplayer_adjust_plan(Side *side, Unit *unit)
{
    int u3;
    Task *task;

    if ((unit->plan->type == PLAN_OFFENSIVE
	 || unit->plan->type == PLAN_COLONIZING
	 || unit->plan->type == PLAN_IMPROVING
	 || unit->plan->type == PLAN_EXPLORATORY)
	&& !mobile(unit->type)
	&& unit->plan->aicontrol
	&& !unit->plan->asleep
	&& unit->plan->tasks == NULL
	) {
	    u3 = preferred_build_type(side, unit, unit->plan->type);
	    if (is_unit_type(u3)) {
		task = unit->plan->tasks;
		if (task == NULL || task->type != TASK_BUILD) {
		    DMprintf("%s directed to build %s\n",
			     unit_desig(unit), u_type_name(u3));
		    net_set_build_task(unit, u3, 2, 0, 0);
		} else {
		    DMprintf("%s already building, leaving alone\n",
			     unit_desig(unit));
		}
		/* Only do one at a time, wait for next go-around for
                   next unit. */
		return FALSE;
	    }
    }
    if (unit->plan->waitingfortasks
	&& unit->plan->aicontrol
	) {
	net_force_replan(side, unit, FALSE);
    }
    if (!unit->plan->reserve
    	&& g_units_may_go_into_reserve()
	&& unit->plan->execs_this_turn > 10 * max(1, u_acp(unit->type))) {
	net_set_unit_reserve(side, unit, TRUE, FALSE);
    }
    /* Look at more units. */
    return TRUE;
}

/* This is a hook that runs after each task is executed. */

static void
iplayer_react_to_task_result(Side *side, Unit *unit, Task *task,
			     TaskOutcome rslt)
{
    int dx, dy, x1, y1, fact;
    Unit *occ;

    /* React to an apparent blockage. */
    if (rslt == TASK_FAILED
	&& task != NULL
	&& task->type == TASK_MOVE_TO
	&& task->retrynum > 2) {
	if (desired_direction_impassable(unit, task->args[0], task->args[1])) {
	    if (unit->occupant) {
		DMprintf("%s blocked while transporting, will sit briefly\n",
			 unit_desig(unit));
		net_set_unit_reserve(side, unit, TRUE, FALSE);
		for_all_occupants(unit, occ) {
		    net_wake_unit(side, occ, FALSE);
		}
		return;
	    }
	    /* Try moving sideways. */
	    if (probability(80)) {
		dx = task->args[0] - unit->x;  dy = task->args[1] - unit->y;
		fact = (flip_coin() ? 50 : -50);
		x1 = unit->x - ((fact * dy) / 100);
		y1 = unit->y + ((fact * dx) / 100);
		if (inside_area(x1, y1))
		  net_push_move_to_task(unit, x1, y1, 1);
	    }
	    return;
	} else if (blocked_by_enemy(unit, task->args[0], task->args[1], TRUE)) {
	    /* (should decide if allowable risk to passengers) */
	    DMprintf("%s blocked by enemy\n", unit_desig(unit));
	    if (!attack_blockage(side, unit, task->args[0], task->args[1], TRUE)) {
		if (blocked_by_enemy(unit, task->args[0], task->args[1], FALSE)) {
		    attack_blockage(side, unit, task->args[0], task->args[1], FALSE);
		} else {
		    /* (should move sideways?) */
		}
	    }
	} else {
	    /* what to do about other failures? */
	}
	return;
    }
    /* React to inability to resupply by trying to build a base. */
    if (rslt == TASK_FAILED
	&& task != NULL
	&& task->type == TASK_RESUPPLY
	&& task->retrynum > 2) {
    	net_set_unit_reserve(side, unit, FALSE, FALSE);
    	build_depot_for_self(side, unit);
    }
}

/* This function is called whenever a new side appears in the game.  It
   mainly needs to make that any allocated data is resized appropriately. */

static void
iplayer_react_to_new_side(Side *side, Side *side2)
{
}

/* At the end of a turn, re-evaluate the plans of some units in case
   the situation changed. */

static void
iplayer_finish_movement(Side *side)
{
    Unit *unit;

    for_all_side_units(side, unit) {
	if (is_active(unit) && ai_controlled(unit)) {
	    iplayer_rethink_plan(unit);
	}
    }
}

/* For units with plans and that are under AI control, consider changing the
   current plan/tasks. */

static void
iplayer_rethink_plan(Unit *unit)
{
    int x1, y1;
    Task *toptask = unit->plan->tasks, *nexttask = NULL;

    if (toptask)
      nexttask = toptask->next;
    if (unit->plan->type == PLAN_OFFENSIVE
        && toptask != NULL
        && toptask->type == TASK_MOVE_TO
        && distance(unit->x, unit->y, toptask->args[0], toptask->args[1])
		>= min(2, u_acp(unit->type))
        && enemy_close_by(unit->side, unit, 1 /* 2 would be better? */, &x1, &y1)
        ) {
	net_push_hit_unit_task(unit, x1, y1, NONUTYPE, -1);
	DMprintf("%s sees enemy close by, will attack it\n", unit_desig(unit));
    }
    /* (should also notice fire opportunities) */
    /* If we see somebody that could be captured and help us explore, set up
       to produce capturers. */
    if (!mobile(unit->type)
	&& (unit->plan->type == PLAN_EXPLORATORY
	    || unit->plan->type == PLAN_OFFENSIVE)
	) {
	int range = 4, rslt, x, y;

	DMprintf("%s searching for useful capture within %d in order to choose build; found ",
	     	 unit_desig(unit), range);
	tmpunit = unit;
	rslt = search_around(unit->x, unit->y, range, useful_captureable_here,
			     &x, &y, 1);
	if (rslt && is_unit_type(tmputype)) {
	    DMprintf("%s at %d,%d", u_type_name(tmputype), x, y);
	    if (toptask != NULL
		&& toptask->type == TASK_BUILD
		&& uu_capture(toptask->args[0], tmputype)
		) {
		/* Already doing the right thing. */
		DMprintf(" - already building %s", u_type_name(toptask->args[0]));
	    } else {
		/* (should find best type that can capture quickly,
		    schedule to build it) */
		DMprintf(" - duhhh, what now?");
	    }
	} else {
	    DMprintf("nothing");
	}
	DMprintf("\n");
    }
}
