package com.bernard.greposimu.controller;

import java.util.EnumSet;
import java.util.HashSet;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;

import org.json.JSONObject;

import com.bernard.greposimu.Utils;
import com.bernard.greposimu.model.game.GameConfig;
import com.bernard.greposimu.model.game.God;
import com.bernard.greposimu.model.game.Resources;
import com.bernard.greposimu.model.game.powers.FuryPower;
import com.bernard.greposimu.model.game.powers.GodPower;
import com.bernard.greposimu.model.game.powers.MultitypePower;
import com.bernard.greposimu.model.game.powers.Power;
import com.bernard.greposimu.model.game.researches.Research;
import com.bernard.greposimu.model.game.units.FightType;
import com.bernard.greposimu.model.game.units.Hero;
import com.bernard.greposimu.model.game.units.Hero.HeroCategory;
import com.bernard.greposimu.model.game.units.NavalUnit;
import com.bernard.greposimu.model.game.units.TerrestrialUnit;
import com.bernard.greposimu.model.game.units.TransportUnit;
import com.bernard.greposimu.model.game.units.Unit;

public class JSONReader {

	public static GameConfig makeGameConfig(JSONObject json) {
		JSONObject godsJ = json.getJSONObject("gods");
        Set<God> gods = new HashSet<>();
        for(String g : godsJ.keySet()) {
        	JSONObject godJ = godsJ.getJSONObject(g);
        	gods.add(new God(godJ.getString("id"), godJ.getString("name")));
        }
        
        JSONObject powersJ = json.getJSONObject("powers");
        Set<Power> powers = new HashSet<>();
        for(String p : powersJ.keySet()) {
        	JSONObject power = powersJ.getJSONObject(p);
        	JSONObject metadefaults = power.isNull("meta_defaults")?null:power.getJSONObject("meta_defaults");
        	if(power.isNull("god_id") || power.getString("god_id").isEmpty()) {
        		// DETECTING TYPE DEPEDENCY
        		boolean dependent =
        				power.optJSONObject("name")!=null ||
						power.optJSONObject("effect")!=null ||
        				power.optJSONObject("description")!=null;
        		String dependentKind = dependent?
        			(metadefaults != null && metadefaults.has("type"))?"type":
    				(metadefaults != null && metadefaults.has("god"))?"god":"unknown"
        			:null;
        	        			
        		if(dependent) {
        			Set<String> kinds = 
        				Optional.ofNullable(power.optJSONObject("name"))
        				.orElseGet(() -> Optional.ofNullable(power.optJSONObject("effect"))
        				.orElseGet(() -> power.optJSONObject("description"))
        				).getJSONObject(dependentKind).keySet();
        			for(String kind : kinds){
            			powers.add(new MultitypePower(
            					power.getString("id")+":"+kind,
            					Optional.ofNullable(power.optString("name")).orElseGet(() -> power.getJSONObject("name").getJSONObject(dependentKind).getString(kind)),
            					Optional.ofNullable(power.optString("description")).orElseGet(() -> power.getJSONObject("description").getJSONObject(dependentKind).getString(kind)),
            					power.isNull("short_effect")?null:power.getString("short_effect"),
    							Optional.ofNullable(power.optString("effect")).orElseGet(() -> power.getJSONObject("effect").getJSONObject(dependentKind).getString(kind)),
            					Utils.toEnumSet(Power.Target.class,power.getJSONArray("targets").toList().stream().map(k -> getPowerTarget((String)k)).collect(Collectors.toSet())),
            					Utils.toEnumSet(Power.Target.class,power.getJSONArray("seeds_to").toList().stream().map(k -> getPowerTarget((String)k)).collect(Collectors.toSet())),
            					getPowerGroup(power.getString("power_group")),
            					power.getBoolean("negative"),
            					power.getInt("power_group_level"),
            					Utils.toEnumSet(Power.Effect.class,power.getJSONArray("effects").toList().stream().map(k -> getPowerEffect((String)k)).collect(Collectors.toSet())),
            					power.getBoolean("ignores_democritus"),
            					Utils.toEnumSet(Power.AreaOfEffect.class,power.getJSONArray("area_of_effect").toList().stream().map(k -> getPowerAOE((String)k)).collect(Collectors.toSet())),
            					getPowerTags(power),
            					power.getInt("lifetime"),
            					metadefaults==null?Map.of():metadefaults.toMap(),
            					power.getString("id")
            			));
        			}
        		}else {
        			powers.add(new Power(
        					power.getString("id"),
        					power.getString("name"),
        					power.getString("description"),
        					power.isNull("short_effect")?null:power.getString("short_effect"),
        					power.getString("effect"),
        					Utils.toEnumSet(Power.Target.class,power.getJSONArray("targets").toList().stream().map(k -> getPowerTarget((String)k)).collect(Collectors.toSet())),
        					Utils.toEnumSet(Power.Target.class,power.getJSONArray("seeds_to").toList().stream().map(k -> getPowerTarget((String)k)).collect(Collectors.toSet())),
        					getPowerGroup(power.getString("power_group")),
        					power.getBoolean("negative"),
        					power.getInt("power_group_level"),
        					Utils.toEnumSet(Power.Effect.class,power.getJSONArray("effects").toList().stream().map(k -> getPowerEffect((String)k)).collect(Collectors.toSet())),
        					power.getBoolean("ignores_democritus"),
        					Utils.toEnumSet(Power.AreaOfEffect.class,power.getJSONArray("area_of_effect").toList().stream().map(k -> getPowerAOE((String)k)).collect(Collectors.toSet())),
        					getPowerTags(power),
        					power.getInt("lifetime"),
        					metadefaults==null?Map.of():metadefaults.toMap()
        			));
        		}
        	} else {
        		if(power.getInt("fury_percentage_cost") != 0) {
        			powers.add(new FuryPower(
        					power.getString("id"),
        					power.getString("name"),
        					power.getString("description"),
        					power.isNull("short_effect")?null:power.getString("short_effect"),
        					Optional.ofNullable(power.optString("effect")).orElseGet(() -> power.optJSONObject("effect").getString("not_cast")),
        					Utils.toEnumSet(Power.Target.class,power.getJSONArray("targets").toList().stream().map(k -> getPowerTarget((String)k)).collect(Collectors.toSet())),
        					Utils.toEnumSet(Power.Target.class,power.getJSONArray("seeds_to").toList().stream().map(k -> getPowerTarget((String)k)).collect(Collectors.toSet())),
        					getPowerGroup(power.getString("power_group")),
        					power.getBoolean("negative"),
        					power.getInt("power_group_level"),
        					Utils.toEnumSet(Power.Effect.class,power.getJSONArray("effects").toList().stream().map(k -> getPowerEffect((String)k)).collect(Collectors.toSet())),
        					power.getBoolean("ignores_democritus"),
        					Utils.toEnumSet(Power.AreaOfEffect.class,power.getJSONArray("area_of_effect").toList().stream().map(k -> getPowerAOE((String)k)).collect(Collectors.toSet())),
        					getPowerTags(power),
        					power.getInt("lifetime"),
        					metadefaults==null?Map.of():metadefaults.toMap(),
        					power.isNull("god_id")?null:gods.stream().filter(g -> g.getId().equals(power.getString("god_id"))).findAny().get(),
        					power.getInt("favor"),
        					power.isNull("temple_level_sum_dependency")?0:power.getInt("temple_level_sum_dependency"),
        					power.getInt("fury_percentage_cost")/100.0
        			));
        		} else {
        			powers.add(new GodPower(
        					power.getString("id"),
        					power.getString("name"),
        					power.getString("description"),
        					power.isNull("short_effect")?null:power.getString("short_effect"),
        					Optional.ofNullable(power.optString("effect")).orElseGet(() -> power.optJSONObject("effect").getString("not_cast")),
        					Utils.toEnumSet(Power.Target.class,power.getJSONArray("targets").toList().stream().map(k -> getPowerTarget((String)k)).collect(Collectors.toSet())),
        					Utils.toEnumSet(Power.Target.class,power.getJSONArray("seeds_to").toList().stream().map(k -> getPowerTarget((String)k)).collect(Collectors.toSet())),
        					getPowerGroup(power.getString("power_group")),
        					power.getBoolean("negative"),
        					power.getInt("power_group_level"),
        					Utils.toEnumSet(Power.Effect.class,power.getJSONArray("effects").toList().stream().map(k -> getPowerEffect((String)k)).collect(Collectors.toSet())),
        					power.getBoolean("ignores_democritus"),
        					Utils.toEnumSet(Power.AreaOfEffect.class,power.getJSONArray("area_of_effect").toList().stream().map(k -> getPowerAOE((String)k)).collect(Collectors.toSet())),
        					getPowerTags(power),
        					power.getInt("lifetime"),
        					metadefaults==null?Map.of():metadefaults.toMap(),
        					power.isNull("god_id")?null:gods.stream().filter(g -> g.getId().equals(power.getString("god_id"))).findAny().get(),
        					power.getInt("favor"),
        					power.isNull("temple_level_sum_dependency")?0:power.getInt("temple_level_sum_dependency")
        			));}
        	}
        }
        
        JSONObject researchesJ = json.getJSONObject("researches");
        Set<Research> researches = new HashSet<>();
        for(String r : researchesJ.keySet()) {
        	JSONObject research = researchesJ.getJSONObject(r);
        	researches.add(new Research(
        		research.getString("id"),
        		research.getString("name"),
        		research.getString("description"),
        		research.isNull("research_dependencies")?Set.of():research.getJSONArray("research_dependencies").toList().stream().map(k -> Utils.getIdentified(researches,(String)k)).collect(Collectors.toSet()),
                research.isNull("building_dependencies")?Map.of():research.getJSONObject("building_dependencies").keySet().stream()
						.collect(Collectors.toMap(Function.identity(), b -> research.getJSONObject("building_dependencies").getInt(b))),
				research.isNull("resources")?null:new Resources(
						research.getJSONObject("resources").getInt("wood"),
						research.getJSONObject("resources").getInt("stone"),
						research.getJSONObject("resources").getInt("iron")),
				research.getInt("required_time"),
				research.getInt("research_points")
        	));
        }
        
        JSONObject unitsJ = json.getJSONObject("units");
        Set<Unit> units = new HashSet<>();
        for(String u : unitsJ.keySet()) {
        	JSONObject unit = unitsJ.getJSONObject(u);
        	if(unit.getBoolean("is_naval")) {
        		if(unit.getInt("capacity")>0) {
        			units.add(new TransportUnit(
        				unit.getString("id"),
        				unit.getString("name"),
        				unit.getString("description"),
        				unit.getInt("population"),
        				unit.getInt("speed"),
        				unit.getString("category").equals("mythological_ground") || unit.getString("category").equals("mythological_naval"),
        				unit.isNull("god_id")?null:gods.stream().filter(g -> g.getId().equals(unit.getString("god_id"))).findAny().orElse(null),
        				unit.isNull("resources")?null:new Resources(
        						unit.getJSONObject("resources").getInt("wood"),
        						unit.getJSONObject("resources").getInt("stone"),
        						unit.getJSONObject("resources").getInt("iron")),
        				unit.getInt("favor"),
        				unit.getInt("build_time"),
        				unit.isNull("research_dependencies")?Set.of():unit.getJSONArray("research_dependencies").toList().stream().map(k -> Utils.getIdentified(researches, (String)k)).collect(Collectors.toSet()),
                        unit.isNull("building_dependencies")?Map.of():unit.getJSONObject("building_dependencies").keySet().stream()
    						.collect(Collectors.toMap(Function.identity(), b -> unit.getJSONObject("building_dependencies").getInt(b))),
        				unit.getInt("attack"),
        				unit.getInt("defense"),
        				unit.getInt("capacity")
        			));
        		} else {
        			units.add(new NavalUnit(
            				unit.getString("id"),
            				unit.getString("name"),
            				unit.getString("description"),
            				unit.getInt("population"),
            				unit.getInt("speed"),
            				unit.getString("category").equals("mythological_ground") || unit.getString("category").equals("mythological_naval"),
            				unit.isNull("god_id")?null:gods.stream().filter(g -> g.getId().equals(unit.getString("god_id"))).findAny().orElse(null),
            				unit.isNull("resources")?null:new Resources(
            						unit.getJSONObject("resources").getInt("wood"),
            						unit.getJSONObject("resources").getInt("stone"),
            						unit.getJSONObject("resources").getInt("iron")),
            				unit.getInt("favor"),
            				unit.getInt("build_time"),
            				unit.isNull("research_dependencies")?Set.of():unit.getJSONArray("research_dependencies").toList().stream().map(k -> Utils.getIdentified(researches,(String)k)).collect(Collectors.toSet()),
                            unit.isNull("building_dependencies")?Map.of():unit.getJSONObject("building_dependencies").keySet().stream()
        						.collect(Collectors.toMap(Function.identity(), b -> unit.getJSONObject("building_dependencies").getInt(b))),
            				unit.getInt("attack"),
            				unit.getInt("defense")
            			));
        		}
        	} else {
        		FightType ft = null;
        		switch(unit.getString("attack_type")) {
        		case "pierce": ft = FightType.PIERCE;break;
        		case "hack": ft = FightType.HACK;break;
        		case "distance": ft = FightType.DISTANCE;break;
        		}
        		units.add(new TerrestrialUnit(
        				unit.getString("id"),
        				unit.getString("name"),
        				unit.getString("description"),
        				unit.getInt("population"),
        				unit.getInt("speed"),
        				unit.getString("category").equals("mythological_ground") || unit.getString("category").equals("mythological_naval"),
        				unit.isNull("god_id")?null:gods.stream().filter(g -> g.getId().equals(unit.getString("god_id"))).findAny().orElse(null),
        				unit.isNull("resources")?null:new Resources(
        						unit.getJSONObject("resources").getInt("wood"),
        						unit.getJSONObject("resources").getInt("stone"),
        						unit.getJSONObject("resources").getInt("iron")),
        				unit.getInt("favor"),
        				unit.getInt("build_time"),
        				unit.isNull("research_dependencies")?Set.of():unit.getJSONArray("research_dependencies").toList().stream().map(k -> Utils.getIdentified(researches, (String)k)).collect(Collectors.toSet()),
                		unit.isNull("building_dependencies")?Map.of():unit.getJSONObject("building_dependencies").keySet().stream()
    						.collect(Collectors.toMap(Function.identity(), b -> unit.getJSONObject("building_dependencies").getInt(b))),
    					unit.getInt("attack"),
        				ft,
        				unit.getInt("def_pierce"),
        				unit.getInt("def_hack"),
        				unit.getInt("def_distance"),
        				(unit.has("booty"))?unit.getInt("booty"):0,
        				unit.getJSONArray("special_abilities").toList().stream().filter(o -> o.equals("flying")).findAny().isPresent()
        			));
        	}
        	
        }
        
        JSONObject heroesJ = json.getJSONObject("heroes");
        Set<Hero> heroes = new HashSet<>();
        for(String h : heroesJ.keySet()) {
        	JSONObject hero = heroesJ.getJSONObject(h);
        	FightType ft = null;
    		switch(hero.getString("attack_type")) {
    		case "pierce": ft = FightType.PIERCE;break;
    		case "hack": ft = FightType.HACK;break;
    		case "distance": ft = FightType.DISTANCE;break;
    		}
    		JSONObject descargs = hero.getJSONObject("description_args").getJSONObject("1");
        	heroes.add(new Hero(
        		hero.getString("id"),
        		hero.getString("name"),
        		hero.getString("description"),
        		hero.getInt("speed"),
        		hero.getInt("attack"),
        		ft,
        		hero.getInt("def_pierce"),
        		hero.getInt("def_hack"),
        		hero.getInt("def_distance"),
        		hero.getInt("booty"),
        		hero.getString("category").equals("war")?HeroCategory.WAR:HeroCategory.WISDOM,
        		hero.getInt("cost"),
        		hero.getString("short_description"),
                descargs.getDouble("value"),
                descargs.getDouble("level_mod")
        	));
        }
        return new GameConfig(gods, units, heroes, researches, powers);
	}
	
	private static EnumSet<Power.Tag> getPowerTags(JSONObject o) {
		Set<Power.Tag> out = new HashSet<Power.Tag>();
		if(o.getBoolean("extendible"))out.add(Power.Tag.EXTENDIBLE);
		if(o.getBoolean("display_amount"))out.add(Power.Tag.DISPLAY_AMOUNT);
		if(o.getBoolean("destructive"))out.add(Power.Tag.DESTRUCTIVE);
		if(o.getBoolean("is_capped"))out.add(Power.Tag.CAPPED);
		if(o.getBoolean("is_fake_power"))out.add(Power.Tag.FAKE_POWER);
		if(o.getBoolean("is_onetime_power"))out.add(Power.Tag.ONETIME_POWER);
		if(o.getBoolean("is_ritual"))out.add(Power.Tag.RITUAL);
		if(o.getBoolean("is_upgradable"))out.add(Power.Tag.UPGRADABLE);
		if(o.getBoolean("is_valid_for_happenings"))out.add(Power.Tag.VALID_FOR_HAPPENINGS);
		if(o.getBoolean("transfer_to_casual_world"))out.add(Power.Tag.TRANSFER_TO_CASUAL_WORLD);
		if(o.getBoolean("wasteable"))out.add(Power.Tag.WASTEABLE);
		if(o.getBoolean("needs_level"))out.add(Power.Tag.NEEDS_LEVEL);
		if(o.getBoolean("passive"))out.add(Power.Tag.PASSIVE);
		if(o.getBoolean("recreate_on_restart"))out.add(Power.Tag.RECREATE_ON_RESTART);
		if(o.getBoolean("removed_on_target_loss"))out.add(Power.Tag.REMOVED_ON_TARGET_LOSS);
		if(o.getBoolean("requires_god"))out.add(Power.Tag.REQUIRES_GOD);
		if(o.getBoolean("no_lifetime"))out.add(Power.Tag.NO_LIFETIME);
		return Utils.toEnumSet(Power.Tag.class,out);
	}
	private static Power.AreaOfEffect getPowerAOE(String s){
		switch(s) {
		case "area_of_effect_build_time": return Power.AreaOfEffect.BUILDTIME;
		case "area_of_effect_commands": return Power.AreaOfEffect.COMMANDS;
		case "area_of_effect_favor": return Power.AreaOfEffect.FAVOR;
		case "area_of_effect_militia": return Power.AreaOfEffect.MILITIA;
		case "area_of_effect_resources": return Power.AreaOfEffect.RESOURCES;
		}
		throw new IllegalArgumentException("I don't know Power AreaOfEffect "+s);
	}
	
	private static Power.Effect getPowerEffect(String s){
		switch(s) {
		case "effects_ground": return Power.Effect.GROUND;
		case "effects_naval": return Power.Effect.NAVAL;
		case "effects_wall": return Power.Effect.WALL;
		}
		throw new IllegalArgumentException("I don't know Power Effect "+s);
	}
	
	private static Power.Group getPowerGroup(String s){
		switch(s) {
		case "attack_boost_group": return Power.Group.ATTACK_BOOST;
		case "attack_ship_attack_boost_group": return Power.Group.ATTACK_SHIP_ATTACK_BOOST;
		case "battle_point_boost_group": return Power.Group.BATTLE_POINT_BOOST;
		case "building_boost_group": return Power.Group.BUILDING_BOOST;
		case "defense_boost_group": return Power.Group.DEFENSE_BOOST;
		case "favor_boost_group": return Power.Group.FAVOR_BOOST;
		case "resource_boost_group": return Power.Group.RESOURCE_BOOST;
		case "unit_boost_group": return Power.Group.UNIT_BOOST;
		case "": return null;
		}
		throw new IllegalArgumentException("I don't know Power Group "+s);
	}
	
	private static Power.Target getPowerTarget(String s){
		switch(s) {
		case "target_alliance": return Power.Target.ALLIANCE;
		case "target_command": return Power.Target.COMMAND;
		case "target_player": return Power.Target.PLAYER;
		case "target_support_command": return Power.Target.SUPPORT_COMMAND;
		case "target_town": return Power.Target.TOWN;
		}
		throw new IllegalArgumentException("I don't know Power Target "+s);
	}
}
