
namespace com.blades.main;

external interface Comparator {}
external interface Runnable {}

/* */
/* ACTIVITY */
/* */
class Activity {
	Aspect objective;
	Aspect[] rules;
	Item[] tools;
	Playbook[] roles;
}

/* */
/* APPROACH */
/* */
class Approach {
	const ASSAULT = "assault"; const DECEPTION = "deception"; const OCCULT = "occult"; const SOCIAL = "social"; const STEALTH = "stealth"; const TRANSPORT = "transport";
	
	isA Aspect;
	Approach(aName) { super(aName, "", Aspect.APPROACH); }
}

/* */
/* DOWNTIME */
/* */
class Downtime {
	const ACQUIRE_ASSET = "acquire asset"; const LONG_TERM_PROJECT = "long-term project"; 
	const RECOVER = "recover"; const REDUCE_HEAT = "reduce heat"; 
	const TRAIN = "train"; const INDULGE_VICE = "indulge vice"; 

	isA Aspect;
	Downtime(aName) { super(aName, "", Aspect.DOWNTIME); }
}

/* */
/* ENTANGLEMENT */
/* */
class Entanglement {
	const ARREST = "arrest"; const COOPERATION = "cooperation"; const DEMONIC_NOTICE = "demonic notice"; const FLIPPED = "flipped"; const GANG_TROUBLE = "gang trouble"; const INTERROGATION = "interrogation"; const QUESTIONING = "questioning"; const REPRISALS = "reprisals"; const RIVALS = "rivals"; const SHOW_OF_FORCE = "show of force"; const UNQUIET_DEAD = "unquiet dead"; const USUAL_SUSPECTS = "usual suspects";

	isA Aspect;
	Entanglement(aName) { super(aName, "", Aspect.ENTANGLEMENT); }
	
	/* */
	public static Entanglement newEntanglement(int heat, int wanted) {
		String s = ARREST;
		
		if (heat < 2 && wanted < 4) s = GANG_TROUBLE;
		else if (heat < 4 && wanted < 4) s = USUAL_SUSPECTS;
		else if (heat == 4 && (wanted == 4 || wanted == 5)) s = REPRISALS;
		else if (heat == 5 && (wanted == 4 || wanted == 5)) s = UNQUIET_DEAD;
		else if ((heat == 4 || heat == 5) && wanted == 6) s = SHOW_OF_FORCE;
		else if (heat > 7 && wanted < 4) s = FLIPPED;
		else if (heat > 5 && wanted < 4) s = INTERROGATION;
		else if (heat > 5 && wanted == 4) s = DEMONIC_NOTICE;
		else if (heat > 5 && wanted == 5) s = SHOW_OF_FORCE;
		else s = ARREST;
		
		return new Entanglement(s);
	}
}

/* */
/* FIASCO */
/* */
class Fiasco {
	const int NUM_OF_ACTORS = 6;

	0..1 -> 6 Rogue;
	Situation[] situations;

	after constructor {
		setup(allRogues);
	}

	private void setup(Rogue[] allRogues) {
		int mod = allRogues.length;
		for (int i = 1; i < mod; i += 2) {
			Rogue a = allRogues[i];
			Rogue b = allRogues[(i + 1) % mod];
			Rogue c = allRogues[(i - 1) % mod];

			RogueShip ab = a.getShip(b, RogueShip.ALLY);
			situations.add(new Situation(ab, new Obstacle(Obstacle.DISTRACTION, ActionRating.next())));

			RogueShip ac = a.getShip(c, RogueShip.ENEMY);
			situations.add(new Situation(ac, new Obstacle(Obstacle.DISTRACTION, ActionRating.next())));
		}
	}
	
	public static void update() {
		new Fiasco(Rogue.getRogues()
				.sorted((r1, r2) -> Integer.compare(r1.getRogueShips().size(), r2.getRogueShips().size()))
				.limit(NUM_OF_ACTORS).toArray(Rogue[]::new));
	}
	
	public static Fiasco newFiasco() {
		return new Fiasco(Rogue.getRogues()
				.sorted((r1, r2) -> Integer.compare(r1.getRogueShips().size(), r2.getRogueShips().size()))
				.limit(NUM_OF_ACTORS).toArray(Rogue[]::new));
	}
}

/* */
/* OBJECTIVE */
/* */
class Objective {
	/* real or symbolic value; relevant _physical_ obstacles; exposure -> suitability of target; ; */
	
	/* Assist: enable another eligible faction to CLIMB */
	/* Climb: advance up the ladder, or secure your hold */
	/* Exonerate: reduce the wanted level of a faction */
	/* Implicate: increase the wanted level of a faction */
	/* Provoke: worsen relationship between two factions */
	/* Rapport: improve relationship between two factions */
	/* Shake: weaken the hold of another group, or make them fall */
	/* Sponsor: create a new faction or reforge a destroyed faction */
	
	const ASSIST = "assist"; const CLIMB = "climb"; 
	const EXONERATE = "exonerate"; const IMPLICATE = "implicate"; 
	const PROVOKE = "provoke"; const RAPPORT = "rapport"; 
	const SHAKE = "shake"; const SPONSOR = "sponsor"; 

	isA Aspect;
	Objective(aName) { super(aName, "", Aspect.OBJECTIVE); }
	/* Opportunity opportunity; */
	/* value; inertia; visibility; accessibility; */
	
	/* Crew target = { opportunity.getTarget() } */
	/* Locale locale = { opportunity.getLocale() } */
	/* Situation situation = { opportunity.getSituation() } */
	
	/* */
	
	/* */
	public static Objective objectify(Opportunity opp) { return new Objective(""); }
}

/* */
/* OBSTACLE */
/* */
class Obstacle {
	/* Hazards, barriers, and distractions are borrowed from FATE: Accelerated. In short, hazards deal damage, barriers must be opposed, and distractions present choices with consequences. */
	const BARRIER = "barrier"; const DISTRACTION = "distraction"; const HAZARD = "hazard"; 
	/* A distraction consists of the Dilemma, the Effects, and an Opposition value (if any). */

	isA Aspect;

	ActionRating rating;

	Obstacle(aName, ActionRating rating) {
		super(aName, "", Aspect.OBSTACLE);
		setRating(rating);
	}
}

/* */
/* OPPORTUNITY */
/* */
class Opportunity {
	depend java.util.Comparator;

	depend java.util.concurrent.PriorityBlockingQueue;
	depend java.util.stream.Collectors;
	depend java.util.stream.Stream;
	
	/* */
	const Comparator<Opportunity> PRIORITY = priority();
	const PriorityBlockingQueue<Opportunity> QUEUE = new PriorityBlockingQueue<Opportunity>(11, PRIORITY);

	/* UNIQUE SET-STREAM */
	const Set<Opportunity> SET = new HashSet<Opportunity>();	
	after constructor { SET.add(this); } after delete { SET.remove(this); }
	private static Stream<Opportunity> getStream() { return SET.stream(); }

	/* */
	lazy UUID id; before getId { if (Util.unless(id)) setId(Util.nextUUID()); } key { id }

	Crew target;
	Locale locale;
	Situation situation;
	long turnCreated = com.blades.main.GameContext.getInstance().getTurn();

	/* "backdoor" implies alternate plan vector */
	lazy Rogue patron;  lazy Crew[] related; lazy Opportunity[] secrets; lazy String backdoor;
	
	/* */
	after constructor { QUEUE.add(this); }
	
	/* */
	/* */
	/* */
	public static void update() {
		/* FIXME - really, I need a better method of generating opportunities */
		if (QUEUE.size() < 6) {
			Iterator<Crew> crits = Crew.getCrews().iterator();
			Iterator<Locale> pits = Locale.getPlaces().iterator();
			Iterator<Situation> sits = Situation
					.getSituations(RogueShip.getRogueShips(RogueShip.ENEMIES).collect(Collectors.toSet())).iterator();

			while (crits.hasNext() && pits.hasNext() && sits.hasNext()) {
				new Opportunity(crits.next(), pits.next(), sits.next());
			}
		}
	}
	
	/* */
	public static Stream<Opportunity> getOpportunities() { return getStream(); }
	public static Stream<Opportunity> getOpportunities(Crew crew) {
		return getOpportunities().filter(opp -> crew.getTurf().contains(opp.getLocale()));
	}
	
	public static Opportunity next(Crew crew) {
		boolean isTarget = Util.unless(QUEUE.isEmpty()) ? QUEUE.peek().getTarget().equals(crew) : true;
		return Util.unless(isTarget) ? QUEUE.poll() : null;
	}
	
	private static Comparator<Opportunity> priority() { return (op1, op2) -> Long.compare(op2.turnCreated, op1.turnCreated); }
}

/* */
/* PLAN */
/* */
class Plan {
	/* */
	* <@>- 1 Rogue mastermind;

	/* */
	Activity activity;
	Aspect approach;
	Rogue[] team;
	CrewShip[] interestedParties;
}

/* */
/* SCENE */
/* */
class Scene {
	/* */
	isA Runnable;

	/* */
	0..1 -> 0..* Aspect aspects;

	/* */
	boolean hasAspect(String aspect) { return getAspects().stream().filter(e -> e.getType().matches(aspect)).count() > 0; }
	Aspect getAspect(String aspect) { return getAspects().stream().filter(e -> e.getType().matches(aspect)).findFirst().get(); }

	/* */
	@Override public void run() {}

	/* */
	public static Scene newScene(Aspect entanglement) {
		Scene scene = new Scene();
		scene.addAspect(entanglement);
		return scene;
	}
}

/* */
/* SCORE */
/* */
class Score {
	depend java.util.Arrays;
	depend java.util.HashSet;
	depend java.util.List;
	depend java.util.Set;
	depend java.util.UUID;
	depend java.util.stream.Collectors;
	depend java.util.stream.Stream;

	/* */
	isA Runnable;

	/* UNIQUE SET-STREAM */
	const Set<Score> SET = new HashSet<Score>();	
	after constructor { SET.add(this); } after delete { SET.remove(this); }
	private static Stream<Score> getStream() { return SET.stream(); }

	/* A Score is a short story, composed of "means, motive, and opportunity." Here, the individual meaning of the terms changes to accomodate the game mechanics. */
	lazy UUID id; before getId { if (Util.unless(id)) setId(Util.nextUUID()); } key { id }

	/* */
	Crew crew;
	Opportunity opportunity; /* motive */
	Plan plan; /* means */
	Situation detail; /* opportunity */

	/* */
	PlaySpace playspace = new PlaySpace();
	name = { String.format("The %s job", opportunity.getTarget().getName()) }
	int rep = { 2 - (getCrew().getTier() - opportunity.getTarget().getTier()) }
	boolean hostileTurf = false; boolean highProfile = false;
	int bodyCount = 0; int coin = 6; int heat = 2;
	Crew target = { getOpportunity().getTarget() }
	boolean undercover = { /* TODO */ false }

	internal List<Runnable> r = Arrays.asList(this::ready, this::engagement, this::operation, this::payoff, this::heat, this::entanglement, this::downtime);
	/* boolean accepted = false; boolean abandoned = false; boolean active = false; */
	
	/* */
	List<Rogue> team = { Arrays.asList(getPlan().getTeam()) };
	List<CrewShip> interestedParties = { Arrays.asList(getPlan().getInterestedParties()) };
	
	/* */
	int getCurrentHeat() {
		int heat = this.heat; 
		heat += crew.getAtWar()? 1 : 0; heat += bodyCount > 0? 2 : 0;
		heat += hostileTurf? 1 : 0; heat += highProfile? 1 : 0;
		return heat;
	}
	
	
	/* */
	@Override void run() { r.stream().forEach(Runnable::run); }
	
	/* */
	public static Stream<Score> getScores() { return getStream(); }
	
	/* score state machine */
	sequence {
		Ready { ready -> /{
			/* */
		} Engagement; }
		Engagement { engagement -> /{
			/* */
			getTeam().forEach(r -> r.setTapped(true));
		} Operation; }
		Operation { operation -> /{
			/* */
		} Payoff; }
		Payoff { payoff -> /{
			/* */
			if (Util.unless(getUndercover())) { getCrew().setRep(getCrew().getRep() + getRep()); }
			/* System.out.println(String.format("%s earned %d rep for %s", getCrew().getName(), getRep(), getName())); */
			getCrew().payoff(this);
			getCrew().advance();
			/* System.out.println(String.format("%s earned %d coin for %s", getCrew().getName(), getCoin(), getName())); */
		} Heat; }
		Heat { heat -> /{
			int currentHeat = getCurrentHeat();
			if (currentHeat > 0) { crew.setHeat(crew.getHeat() + currentHeat); }
			getInterestedParties().stream().filter(e -> Util.unless(e.contains(crew))).limit(3).forEach(e -> {
				int value = e.hasStatus(CrewShip.ALLIES) ? -1 : e.hasStatus(CrewShip.ENEMIES) ? 1 : 0;
				CrewShip ship = e.getOther(getTarget()).getShip(crew, value);
				/* System.out.println(String.format("> (%2s) %s", value, ship.getShipname())); */
			});
		} Entanglement; }
		Entanglement { entanglement -> /{
			/* */
			getCrew().entanglement(this);
		} Downtime; }
		Downtime { downtime -> /{
			/* */
			int actions = (getCrew().getAtWar()) ? 1 : 2;
			getTeam().forEach(r -> r.doDowntime(actions));
			System.out.println(String.format("%s completed the %s job", crew.getName(), opportunity.getTarget().getName()));
			crew.setScore(null);
		} Final; }
	}
}

/* */
/* SITUATION */
/* */
class Situation {
	depend java.util.HashSet;
	depend java.util.Set;
	depend java.util.stream.Stream;

	/* UNIQUE SET-STREAM */
	const Set<Situation> SET = new HashSet<Situation>();	
	after constructor { SET.add(this); } after delete { SET.remove(this); }
	private static Stream<Situation> getStream() { return SET.stream(); }

	/* */
	RogueShip ship;
	Aspect aspect;
	
	/* */
	long turnCreated = com.blades.main.GameContext.getInstance().getTurn();
	
	key { ship, aspect }
	
	/* static */
	public static Situation newSituation(RogueShip ship, Aspect aspect) {
		return new Situation(ship, aspect);
	}
	
	public static void update() {
	}
	
	public static Stream<Situation> getSituations() { return getStream(); }
	public static Stream<Situation> getSituations(Rogue rogue) { return getSituations().filter(s -> s.getShip().contains(rogue)); }
	public static Stream<Situation> getSituations(RogueShip ship) { return getSituations().filter(s -> s.getShip().equals(ship)); }
	public static Stream<Situation> getSituations(Set<RogueShip> set) { return getSituations().filter(e -> set.contains(e.getShip())); }
}
