package ca.cosc3p91.a4.game;

import ca.cosc3p91.a4.gameobjects.*;
import ca.cosc3p91.a4.gameobjects.factory.BuildingFactory;
import ca.cosc3p91.a4.gameobjects.factory.InhabitantFactory;
import ca.cosc3p91.a4.player.*;
import ca.cosc3p91.a4.userinterface.GameDisplay;
import ca.cosc3p91.a4.util.ChallengeAdapter;

import java.beans.XMLEncoder;
import java.io.BufferedOutputStream;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Random;

public class GameEngine {

    public static final double GOLD_FACTOR = 5;
    public static final double IRON_FACTOR = 1;
    public static final double WOOD_FACTOR = 0.1;

    private float pillageFactor = 0.5f;

    private int currentTime;

    private final Random random = new Random(System.nanoTime());

    public GameDisplay view;

    public GameEngine() {
    }

    public void attackVillage(Map attacking, Map defending) {
//        int defenseiveCounter = 1;
//        int inhabCounter = 0;
//        for (Building b : map.contains)
//            if (b instanceof DefenseBuilding)
//                defenseiveCounter++;
//        for (Inhabitant i : map.inhabitants)
//            if (i instanceof Infantry)
//                inhabCounter++;
//        pillageFactor = (float) inhabCounter / (float) defenseiveCounter;
//        if (pillageFactor < 0)
//            pillageFactor = 0;
//        if (pillageFactor > 1)
//            pillageFactor = 1;
//        this.map.getTownHall().addWood((int) (map.getTownHall().getCurrentWood() * pillageFactor));
//        this.map.getTownHall().addIron((int) (map.getTownHall().getCurrentIron() * pillageFactor));
//        this.map.getTownHall().addGold((int) (map.getTownHall().getCurrentGold() * pillageFactor));
        ChallengeAdapter adapter = new ChallengeAdapter(attacking);
        adapter.attack(defending);
    }

    public Map generateInitialMap(){
        return new Map(new CasaDeNarino(1, VillageHallStages.villageStages[0]), 30);
    }

    public Map generateMap(Map map) {
        Map initialMap = generateInitialMap();

        CasaDeNarino hall = initialMap.getTownHall();

        // generate a similar town hall
        int levelChange = random.nextInt(2) - 1;
        int nextLevel = map.getTownHall().getLevel() + levelChange;
        // only need to change if the new village level is higher than initial
        if (nextLevel > 0)
            hall.upgrade(VillageHallStages.villageStages[nextLevel]);

        hall.addWood(map.getTownHall().getCurrentWood() + random.nextInt(500) - 150);
        hall.addIron(map.getTownHall().getCurrentIron() + random.nextInt(500) - 150);
        hall.addGold(map.getTownHall().getCurrentGold() + random.nextInt(500) - 150);

        int buildingCount = map.contains.size();

        int saulGoodMines = 0;
        int ironMines = 0;
        int woodMines = 0;
        int archerTowers = 0;
        int cannons = 0;

        // count buildings in our map
        for (Building b : map.contains){
            if (b instanceof SaulGoodMine)
                saulGoodMines++;
            else if (b instanceof IronMine)
                ironMines++;
            else if (b instanceof LumberMine)
                woodMines++;
            else if (b instanceof ArcherTower)
                archerTowers++;
            else if (b instanceof  Cannon)
                cannons++;
        }

        // variate
        saulGoodMines += random.nextInt();;
        ironMines += random.nextInt();
        woodMines += random.nextInt();
        archerTowers += random.nextInt();
        cannons += random.nextInt();

        // generate a map with a similar number of buildings
        for (int i = 0; i < Math.max(buildingCount + random.nextInt(5) - 2, 1); i++) {
            int selection = random.nextInt(5);
            // select a random building. Doing it this way because if we build based on buildings we have
            // then the maps will be VERY boring as they would be all the same
            switch (selection) {
                case 0:
                    initialMap.contains.add(new LumberMine(ResourceStages.woodStages[random.nextInt(ResourceStages.woodStages.length)]));
                    break;
                case 1:
                    if (ironMines > 0)
                        initialMap.contains.add(new IronMine(ResourceStages.ironStages[random.nextInt(ResourceStages.ironStages.length)]));
                    break;
                case 2:
                    if (saulGoodMines > 0)
                        initialMap.contains.add(new SaulGoodMine(ResourceStages.goldStages[random.nextInt(ResourceStages.goldStages.length)]));
                    break;
                case 3:
                    initialMap.contains.add(new ArcherTower());
                    break;
                case 4:
                    initialMap.contains.add(new Cannon());
                    break;
                default:
                    break;
            }
        }

        return initialMap;
    }

    public int getScore(Map map) {
        CasaDeNarino hall = map.getTownHall();
        int score = (int)(hall.getCurrentGold() * GOLD_FACTOR + hall.getCurrentIron() * IRON_FACTOR + hall.getCurrentWood() * WOOD_FACTOR);
        score += map.contains.size();
        score += map.inhabitants.size();
        return score;
    }

    public synchronized void updateMap(Map map) {
        for (int i = 0; i < map.contains.size(); i++) {
            if ((map.contains.get(i) instanceof ResourceBuilding)) {
                ((ResourceBuilding) map.contains.get(i)).update(map.getTownHall());
            }
        }
    }

    public synchronized boolean build (Map map, String buildingArg) {
        BuildingFactory bfactory = new BuildingFactory();
        Building type = bfactory.getBuilding(buildingArg);
        return map.build(new Tile(), type);
    }

    public boolean train (Map map, String inhabitantArgs) {
        InhabitantFactory ifactory = new InhabitantFactory();
        Inhabitant type = ifactory.getInhabitant(inhabitantArgs);
        return  map.train(type);
    }

    public synchronized boolean upgradeBuilding (Map map, int buildingIndex) {
        return map.upgradeBuilding(buildingIndex);
    }

    public boolean upgradeInhabitant (Map map, int inhabitantIndex) {
        return map.upgradeInhabitant(inhabitantIndex);
    }

    /*
    @Override
    public void run() {
        String in;

        view = new GameDisplay();
        view.printVillageState(this.map,"Current Village State");
        view.printGameMenu();

        Map exploringMap = null;
        boolean deleteMyHeart = true;
        while (running) {
            try {
                if ((in = view.nextInput()) != null) {
                    String[] args = in.split(" ");

                    view.printLastInput();
                    // reset the map if they aren't exploring
                    if (in.charAt(0) != '4')
                        deleteMyHeart = true;
                    switch (in.charAt(0)) {
                        case '1':
                            if (args.length < 2) {
                                System.err.println("Args must include type!");
                            } else {
                                BuildingFactory bfactory = new BuildingFactory();
                                Building type = bfactory.getBuilding(args[1]);
                                if (type == null)
                                    System.err.println("Args are not a valid building!");
                                else if (this.map.build(new Tile(), type) ) {
                                    System.out.println(type.getClass().getSimpleName()+" successfully built\n");
                                } else
                                    System.out.println("Missing resources to build "+type.getClass().getSimpleName());
                            }
                            break;
                        case '2':
                            if (args.length < 2) {
                                System.err.println("Args must include type!");
                            } else {
                                InhabitantFactory ifactory = new InhabitantFactory();
                                Inhabitant type = ifactory.getInhabitant(args[1]);
                                if (type == null)
                                    System.err.println("Args are not a valid inhabitant!");
                                else if (this.map.train(type) ) {
                                    System.out.println("successfully trained a(n) "+type.getClass().getSimpleName());
                                } else System.out.println("Missing gold to train "+type.getClass().getSimpleName());
                            }
                            break;
                        case '3':
                            if (args.length < 2) {
                                System.err.println("Args must include type!");
                            } else {
                                int unitIndex = Integer.parseInt(args[1].substring(1));

                                if (unitIndex < 0) {
                                    System.err.println("Invalid Index");
                                    break;
                                }

                                if (args[1].contains("i") && (unitIndex < map.inhabitants.size()) ) {
                                    if ( map.upgradeInhabitant(unitIndex) ) {
                                        System.out.println("successfully upgraded a(n) "+map.inhabitants.get(unitIndex).getClass().getSimpleName());
                                    } else System.out.println("Missing Resources to upgrade "+map.inhabitants.get(unitIndex).getClass().getSimpleName());
                                } else if (args[1].contains("b") && (unitIndex < map.contains.size()) ) {
                                    if ( map.upgradeBuilding(unitIndex) ) {
                                        System.out.println("successfully upgraded a(n) "+map.contains.get(unitIndex).getClass().getSimpleName());
                                    } else System.out.println("Missing Resources to upgrade "+map.contains.get(unitIndex).getClass().getSimpleName());
                                } else {
                                    System.err.println("Args are not a valid unit!");
                                }
                            }
                            break;
                        case '4':
                            deleteMyHeart = false;
                            exploringMap = generateMap();
                            view.printVillageState(exploringMap,"Other Village");
                            break;
                        case '7':
                            if (exploringMap != null)
                                attackVillage(exploringMap);
                            else
                                System.out.println("Error: Explored map is null. Have you explored last command?");
                            break;
                        case '5':
                            view.printVillageState(this.map,"Home Village");
                            break;
                        case '6':
                            System.exit(0);
                            break;
                        default:
                            break;
                    }
                    view.printGameMenu();
                }
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
            if (deleteMyHeart)
                exploringMap = null;
        }
    } */

    public void save(String file, Map map){
        try (XMLEncoder encoder = new XMLEncoder(new BufferedOutputStream(Files.newOutputStream(Paths.get(file))))) {
            map.contains.forEach(encoder::writeObject);
            map.inhabitants.forEach(encoder::writeObject);
            System.out.println("Wrote map");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

}
