/**
 *  Copyright (C) 2002-2012  The FreeCol Team
 *
 *  This file is part of FreeCol.
 *
 *  FreeCol 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 of the License, or
 *  (at your option) any later version.
 *
 *  FreeCol is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with FreeCol.  If not, see <http://www.gnu.org/licenses/>.
 */

package net.sf.freecol.server.model;

import java.util.ArrayList;
import java.util.List;

import net.sf.freecol.common.model.BuildingType;
import net.sf.freecol.common.model.Colony;
import net.sf.freecol.common.model.ColonyTile;
import net.sf.freecol.common.model.EquipmentType;
import net.sf.freecol.common.model.Game;
import net.sf.freecol.common.model.GoodsType;
import net.sf.freecol.common.model.Map;
import net.sf.freecol.common.model.Map.Direction;
import net.sf.freecol.common.model.Player;
import net.sf.freecol.common.model.Tile;
import net.sf.freecol.common.model.TileImprovement;
import net.sf.freecol.common.model.TileImprovementType;
import net.sf.freecol.common.model.TileType;
import net.sf.freecol.common.model.Unit;
import net.sf.freecol.common.model.UnitType;
import net.sf.freecol.common.model.UnitTypeChange.ChangeType;
import net.sf.freecol.common.model.WorkLocation;
import net.sf.freecol.server.ServerTestHelper;
import net.sf.freecol.server.control.InGameController;
import net.sf.freecol.util.test.FreeColTestCase;
import net.sf.freecol.util.test.MockPseudoRandom;


public class ServerUnitTest extends FreeColTestCase {

    private static final BuildingType townHallType
        = spec().getBuildingType("model.building.townHall");

    private static final EquipmentType horsesType
        = spec().getEquipmentType("model.equipment.horses");
    private static final EquipmentType toolsType
        = spec().getEquipmentType("model.equipment.tools");

    private static final GoodsType foodType
        = spec().getPrimaryFoodType();
    private static final GoodsType grainType
        = spec().getGoodsType("model.goods.grain");

    private static final TileImprovementType road
        = spec().getTileImprovementType("model.improvement.road");
    private static final TileImprovementType plow
        = spec().getTileImprovementType("model.improvement.plow");
    private static final TileImprovementType clear
        = spec().getTileImprovementType("model.improvement.clearForest");

    private static final TileType plains
        = spec().getTileType("model.tile.plains");
    private static final TileType savannah
        = spec().getTileType("model.tile.savannah");
    private static final TileType savannahForest
        = spec().getTileType("model.tile.tropicalForest");

    private static final UnitType colonistType
        = spec().getUnitType("model.unit.freeColonist");
    private static final UnitType expertFarmerType
        = spec().getUnitType("model.unit.expertFarmer");
    private static final UnitType pioneerType
        = spec().getUnitType("model.unit.hardyPioneer");
    private static final UnitType soldierType
        = spec().getUnitType("model.unit.veteranSoldier");


    public void testToggleHorses() {
        Map map = getTestMap(plains);
        Game game = ServerTestHelper.startServerGame(map);

        Player dutch = game.getPlayer("model.nation.dutch");
        Tile tile1 = map.getTile(5, 8);
        tile1.setExploredBy(dutch, true);
        ServerUnit scout = new ServerUnit(game, tile1, dutch, colonistType);

        // make sure unit has all moves left
        ServerTestHelper.newTurn();

        assertEquals(scout.getInitialMovesLeft(), scout.getMovesLeft());
        int colonistMoves = scout.getMovesLeft();
        scout.changeEquipment(horsesType, 1);

        ServerTestHelper.newTurn();

        assertTrue("Scout should have more moves than a colonist",
                   scout.getMovesLeft() > colonistMoves);
        scout.changeEquipment(horsesType, -1);

        ServerTestHelper.newTurn();

        assertEquals(scout.getMovesLeft(), colonistMoves);
    }

    /**
     * Test Plowing with a hardy pioneer
     */
    public void testDoAssignedWorkHardyPioneerPlowPlain() {
        Map map = getTestMap(plains);
        Game game = ServerTestHelper.startServerGame(map);
        InGameController igc = ServerTestHelper.getInGameController();

        ServerPlayer dutch = (ServerPlayer)game.getPlayer("model.nation.dutch");
        Tile plain = map.getTile(5, 8);
        plain.setExploredBy(dutch, true);
        plain.setOwner(dutch);

        ServerUnit hardyPioneer = new ServerUnit(game, plain, dutch,
                                                 pioneerType);

        // Before
        assertFalse(plain.hasImprovement(plow));
        assertEquals(3, hardyPioneer.getMovesLeft());
        assertEquals(Unit.UnitState.ACTIVE, hardyPioneer.getState());
        assertEquals(-1, hardyPioneer.getWorkLeft());
        assertEquals(100, hardyPioneer.getEquipmentCount(toolsType) * 20);

        //TileImprovement plowImprovement
        //    = new TileImprovement(game, plain, plow);
        //plain.add(plowImprovement);
        igc.changeWorkImprovementType(dutch, hardyPioneer, plow);

        assertFalse(plain.hasImprovement(plow));
        assertEquals(0, hardyPioneer.getMovesLeft());
        assertEquals(Unit.UnitState.IMPROVING, hardyPioneer.getState());
        assertEquals(5, hardyPioneer.getWorkLeft());
        assertEquals(100, hardyPioneer.getEquipmentCount(toolsType) * 20);

        // Advance to finish
        while (hardyPioneer.getWorkLeft() > 0) {
            ServerTestHelper.newTurn();
        }

        // Pioneer finished work
        assertTrue(plain.hasImprovement(plow));
        assertEquals(0, hardyPioneer.getMovesLeft());
        assertEquals(Unit.UnitState.ACTIVE, hardyPioneer.getState());
        assertEquals(-1, hardyPioneer.getWorkLeft());
        assertEquals(80, hardyPioneer.getEquipmentCount(toolsType) * 20);
    }

    public void testColonyProfitFromEnhancement() {
        Map map = getTestMap(plains);
        Game game = ServerTestHelper.startServerGame(map);
        InGameController igc = ServerTestHelper.getInGameController();

        ServerPlayer dutch = (ServerPlayer)game.getPlayer("model.nation.dutch");
        map.getTile(5, 8).setExploredBy(dutch, true);
        map.getTile(6, 8).setExploredBy(dutch, true);
        Tile plain58 = map.getTile(5, 8);

        //assertEquals(2, dutch.getDifficulty().getIndex());
        //assertEquals("model.difficulty.medium", dutch.getDifficulty().getId());
        assertEquals(6, spec().getInteger("model.option.badGovernmentLimit"));
        assertEquals(10, spec().getInteger("model.option.veryBadGovernmentLimit"));

        // Found colony on 6,8
        ServerUnit soldier = new ServerUnit(game, map.getTile(6, 8), dutch,
                                            soldierType);

        ServerColony colony = new ServerColony(game, dutch, "New Amsterdam",
                                               soldier.getTile());
        dutch.addSettlement(colony);

        soldier.setWorkType(foodType);
        nonServerBuildColony(soldier, colony);
        soldier.setLocation(colony.getColonyTile(plain58));
        ServerUnit hardyPioneer = new ServerUnit(game, plain58, dutch,
                                                 pioneerType);

        // Before
        assertEquals(0, colony.getGoodsCount(foodType));
        assertEquals(2, colony.getFoodConsumption());
        assertEquals(5 + 5, colony.getFoodProduction());
        assertFalse(plain58.hasImprovement(plow));
        assertEquals(0, colony.getProductionBonus());
        assertEquals("" + soldier.getLocation(), colony.getColonyTile(map.getTile(5, 8)), soldier.getLocation());

        // One turn to check production
        ServerTestHelper.newTurn();

        assertFalse(plain58.hasImprovement(plow));
        assertEquals(8, colony.getGoodsCount(foodType));
        assertEquals(2, colony.getFoodConsumption());
        assertEquals(0, colony.getProductionBonus());
        assertEquals(5 + 5, colony.getFoodProduction());

        // Start Plowing
        //TileImprovement plowImprovement
        //    = new TileImprovement(game, plain58, plow);
        //plain58.add(plowImprovement);
        igc.changeWorkImprovementType(dutch, hardyPioneer, plow);

        int n = 0;
        while (hardyPioneer.getWorkLeft() > 0) {
            ServerTestHelper.newTurn();
            n++;
        }

        assertTrue(plain58.hasImprovement(plow));
        // Production for next turn is updated
        assertEquals(5 + 6, colony.getFoodProduction());
        assertEquals(2, colony.getFoodConsumption());
        // But in only 10 - 2 == 8 are added from last turn
        assertEquals(8 + n * 8, colony.getGoodsCount(foodType));

        // In game, this should happen via a Tile update
        colony.invalidateCache();
        // Advance last turn
        ServerTestHelper.newTurn();

        assertTrue(plain58.hasImprovement(plow));
        assertEquals(5 + 6, colony.getFoodProduction());
        assertEquals(2, colony.getFoodConsumption());
        assertEquals(8 + n * 8 + 9, colony.getGoodsCount(foodType));
    }

    /**
     * Test Building a road with a hardy pioneer.
     *
     * The road is available directly, but the pioneer can only move on the next
     * turn.
     *
     */
    public void testDoAssignedWorkHardyPioneerBuildRoad() {
        Map map = getTestMap(savannahForest);
        Game game = ServerTestHelper.startServerGame(map);
        InGameController igc = ServerTestHelper.getInGameController();

        ServerPlayer dutch = (ServerPlayer)game.getPlayer("model.nation.dutch");
        Tile tile = map.getTile(5, 8);
        map.getTile(5, 8).setExploredBy(dutch, true);

        ServerUnit hardyPioneer1 = new ServerUnit(game, tile, dutch,
                                                  pioneerType);
        ServerUnit hardyPioneer2 = new ServerUnit(game, tile, dutch,
                                                  pioneerType);
        ServerUnit hardyPioneer3 = new ServerUnit(game, tile, dutch,
                                                  pioneerType);

        // Before
        assertEquals(false, tile.hasRoad());
        assertEquals(3, hardyPioneer1.getMovesLeft());
        assertEquals(-1, hardyPioneer1.getWorkLeft());
        assertEquals(100, hardyPioneer1.getEquipmentCount(toolsType) * 20);
        assertEquals(Unit.UnitState.ACTIVE, hardyPioneer1.getState());
        assertEquals(3, hardyPioneer2.getMovesLeft());
        assertEquals(-1, hardyPioneer2.getWorkLeft());
        assertEquals(100, hardyPioneer2.getEquipmentCount(toolsType) * 20);
        assertEquals(Unit.UnitState.ACTIVE, hardyPioneer2.getState());

        // Now do it
        tile.setOwner(dutch);
        igc.changeWorkImprovementType(dutch, hardyPioneer1, road);
        igc.changeWorkImprovementType(dutch, hardyPioneer2, road);
        igc.changeWorkImprovementType(dutch, hardyPioneer3, clear);
        assertEquals(6, hardyPioneer1.getWorkLeft());
        assertEquals(6, hardyPioneer2.getWorkLeft());
        assertEquals(8, hardyPioneer3.getWorkLeft());

        TileImprovement roadImprovement = tile.getRoad();
        while (roadImprovement.getTurnsToComplete() > 0) {
            ServerTestHelper.newTurn();
        }

        // After: both pioneers building road have used up their tools
        assertTrue(tile.hasRoad());
        assertTrue(roadImprovement.isComplete());
        assertEquals(savannahForest, tile.getType());

        //assertEquals(0, hardyPioneer1.getMovesLeft());
        assertEquals(-1, hardyPioneer1.getWorkLeft());
        assertEquals(Unit.UnitState.ACTIVE, hardyPioneer1.getState());

        //assertEquals(0, hardyPioneer2.getMovesLeft());
        assertEquals(-1, hardyPioneer2.getWorkLeft());
        assertEquals(Unit.UnitState.ACTIVE, hardyPioneer2.getState());

        assertEquals(180, 20 * (hardyPioneer1.getEquipmentCount(toolsType)
                                + hardyPioneer2.getEquipmentCount(toolsType)));

        // Pioneer clearing forest is not affected
        assertEquals(3, hardyPioneer3.getMovesLeft());
        assertEquals(4, hardyPioneer3.getWorkLeft());
        assertEquals(Unit.UnitState.IMPROVING, hardyPioneer3.getState());
        assertEquals(100, hardyPioneer3.getEquipmentCount(toolsType) * 20);

        // Finish
        while (hardyPioneer3.getWorkLeft() > 0) {
            ServerTestHelper.newTurn();
        }

        assertEquals(savannah, tile.getType());
        assertEquals(0, hardyPioneer3.getMovesLeft());
        assertEquals(-1, hardyPioneer3.getWorkLeft());
        assertEquals(Unit.UnitState.ACTIVE, hardyPioneer3.getState());
        assertEquals(80, hardyPioneer3.getEquipmentCount(toolsType) * 20);
    }

    public void testUnitGetsExperienceThroughWork() {
        Map map = getTestMap();
        Game game = ServerTestHelper.startServerGame(map);

        Colony colony = getStandardColony();
        Unit colonist = colony.getUnitList().get(0);

        assertEquals("Colonist should not have any experience",
                     0, colonist.getExperience());

        // colonist either in building or colony work tile
        WorkLocation loc = colonist.getWorkLocation();
        if (loc == null) loc = colonist.getWorkTile();

        // produces goods
        ServerTestHelper.newTurn();

        assertTrue("Colonist should have gained some experience",
                   colonist.getExperience() > 0);
    }

    public void testUnitPromotionWorkingInWorkTile() {
        Map map = getTestMap(plains);
        Game game = ServerTestHelper.startServerGame(map);

        Colony colony = getStandardColony();
        assertTrue("Colony should only have 1 colonist for test setup",
                   colony.getUnitCount() == 1);

        Unit colonist = colony.getUnitList().get(0);
        String errMsg = "Error setting test, colonist should not be an expert";
        assertTrue(errMsg, colonist.getType() == colonistType);

        // set colonist as farmer
        Tile tile = colony.getTile().getNeighbourOrNull(Direction.N);
        assertTrue(colony.getOwner().canOwnTile(tile));
        ColonyTile colonyTile = colony.getColonyTile(tile);
        if (colonyTile.getUnit() != null) {
            colonyTile.getUnit().setLocation(colony.getBuilding(townHallType));
        }
        assertTrue(colonyTile.canBeWorked());
        colonist.setLocation(colonyTile);
        colonist.setWorkType(grainType);
        assertEquals("Wrong work allocation",
                     grainType, colonist.getWorkType());
        assertEquals(colonyTile.getUnit(), colonist);
        // Will colonist gain experience?
        assertTrue(colonyTile.getProductionOf(colonist.getWorkType()) > 0);
        // Can colonist be upgraded
        UnitType learn = spec().getExpertForProducing(colonist.getWorkType());
        assertNotNull(learn);
        assertTrue(learn != colonist.getType());
        assertTrue(colonist.getType().canBeUpgraded(learn,
                                                    ChangeType.EXPERIENCE));

        // set some experience
        int expectXP = 10;
        colonist.modifyExperience(expectXP);
        assertEquals("Wrong colonist experience",
                     expectXP, colonist.getExperience());

        // We need a deterministic random
        List<Integer> setValues = new ArrayList<Integer>();
        setValues.add(1);
        MockPseudoRandom mockRandom = new MockPseudoRandom(setValues, true);
        ServerTestHelper.setRandom(mockRandom);

        // Verify initial state
        boolean isExpert = colonist.getType() == expertFarmerType;
        assertFalse("Unit should not be an expert", isExpert);

        // Make upgrade
        ServerTestHelper.newTurn();
        assertTrue(colonist.getExperience() > expectXP);

        // verify upgrade
        isExpert = colonist.getType() == expertFarmerType;
        assertTrue("Unit should now be an expert", isExpert);
    }

    public void testExposeResource() {
        Map map = getTestMap(savannahForest);
        Game game = ServerTestHelper.startServerGame(map);
        InGameController igc = ServerTestHelper.getInGameController();

        ServerPlayer dutch = (ServerPlayer)game.getPlayer("model.nation.dutch");
        Tile tile = map.getTile(5, 8);
        tile.setOwner(dutch);
        tile.setExploredBy(dutch, true);

        // Almost clear the tile
        ServerUnit hardyPioneer = new ServerUnit(game, tile, dutch,
                                                 pioneerType, toolsType);
        //TileImprovement clearImprovement
        //    = new TileImprovement(game, tile, clear);
        //tile.add(clearImprovement);
        igc.changeWorkImprovementType(dutch, hardyPioneer, clear);

        // Verify initial state
        assertEquals(8, hardyPioneer.getWorkLeft());
        assertEquals(savannahForest, tile.getType());
        assertFalse(tile.hasResource());

        // Almost finish clearing
        ServerTestHelper.newTurn();
        ServerTestHelper.newTurn();
        ServerTestHelper.newTurn();

        // We need a deterministic random
        List<Integer> setValues = new ArrayList<Integer>();
        setValues.add(1);
        MockPseudoRandom mockRandom = new MockPseudoRandom(setValues, true);
        ServerTestHelper.setRandom(mockRandom);

        // Finish clearing
        ServerTestHelper.newTurn();

        // Verify clearing succeeded and has revealed a resource
        assertEquals(savannah, tile.getType());
        assertTrue(tile.hasResource());
    }
}
