/**
 *  Copyright (C) 2002-2022   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.generator;

import java.io.EOFException;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.util.logging.Level;
import java.util.logging.Logger;

import net.sf.freecol.common.model.Game;
import net.sf.freecol.common.model.Tile;
import net.sf.freecol.common.model.TileImprovement;
import net.sf.freecol.common.model.TileImprovementStyle;
import net.sf.freecol.common.model.TileImprovementType;
import net.sf.freecol.common.model.TileItemContainer;
import net.sf.freecol.common.model.TileType;
import net.sf.freecol.common.model.Map.Layer;
import net.sf.freecol.common.model.Specification;


/**
 * Just pass the name of a Colonization map file (with extension ".MP").
 *
 * The map file starts with a six-byte header. Byte zero encodes the
 * map width, byte two encodes the map height. The function of the
 * other bytes is unknown, their values, however, are fixed. The
 * header is followed by three "layers", each the size of the map. The
 * first "layer" encodes the terrain type. The function of the other
 * layers is unknown. They are filled with zero bytes.
 *
 * It seems that the least significant three bits encode the basic
 * terrain type, the next two bits encode the forest overlay and
 * the special tile types ice, ocean and sea lanes. The three most
 * significant bits encode combinations of the hill, mountain and
 * river overlays.
 *
 * bits 0-2: tile type
 * bit 3 (8): forest
 * bit 4 (16): forest
 * bits 3+4 (24): special, values larger than 26 are not defined
 *
 * bits 5-7: overlays
 * 0: nothing
 * 1: hill
 * 2: minor river
 * 3: hill + minor river (extremely rare)
 * 4: nothing
 * 5: mountain
 * 6: major river
 * 7: mountain + major river (never seen)
 */
public class ColonizationMapLoader implements MapLoader {

    private static final Logger logger = Logger.getLogger(ColonizationMapLoader.class.getName());

    public static final int WIDTH = 0;
    public static final int HEIGHT = 2;
    public static final int OCEAN = 25;
    public static final int HIGH_SEAS = 26;

    /**
     * A String array of title types. Each represents
     *      a single type of tile available in FreeCol.
     */
    private static final String[] tiletypes = {
        "tundra",
        "desert",
        "plains",
        "prairie",
        "grassland",
        "savannah",
        "marsh",
        "swamp",
        "borealForest",
        "scrubForest",
        "mixedForest",
        "broadleafForest",
        "coniferForest",
        "tropicalForest",
        "wetlandForest",
        "rainForest",
        "borealForest",
        "scrubForest",
        "mixedForest",
        "broadleafForest",
        "coniferForest",
        "tropicalForest",
        "wetlandForest",
        "rainForest",
        "arctic",
        "ocean",
        "highSeas",
    };


    /**
     * A byte array of file headers
     */
    private static final byte[] header = {
        58, 0, 72, 0, 4, 0
    };

    private byte[] layer1;


    public ColonizationMapLoader(File file) throws IOException {

        try (RandomAccessFile reader = new RandomAccessFile(file, "r")) {
            reader.readFully(header);

            int size = header[WIDTH] * header[HEIGHT];
            layer1 = new byte[size];
            reader.readFully(layer1);
        } catch (EOFException ee) {
            logger.log(Level.SEVERE, "File (" + file + ") is too short.", ee);
        } catch (FileNotFoundException fe) {
            
            logger.log(Level.SEVERE, "File (" + file + ") was not found.", fe);
        } catch (IOException e) {
            logger.log(Level.SEVERE, "File (" + file + ") is corrupt and cannot be read.", e);
        }

    }

    /**
     * {@inheritDoc}
     *
     * @return The highest {@code Layer} value
     */
    @Override
    public Layer loadMap(Game game, Layer layer) {
        Specification spec = game.getSpecification();
        Tile[][] tiles = new Tile[header[WIDTH]][header[HEIGHT]];
        Layer highestLayer = layer.compareTo(getHighestLayer()) < 0
            ? layer : getHighestLayer();
        int index = 0;
        TileType tileType = null;
        if (highestLayer == Layer.LAND) {
            // import only the land / water distinction
            for (int y = 0; y < header[HEIGHT]; y++) {
                for (int x = 0; x < header[WIDTH]; x++) {
                    int decimal = layer1[index] & 0xff;
                    int terrain = decimal & 0b11111;
                    tileType = (terrain == OCEAN || terrain == HIGH_SEAS) ?
                        TileType.WATER : TileType.LAND;
                    index++;
                }
            }
        } else {
            TileImprovementType riverType = spec.getTileImprovementType("model.improvement.river");
            for (int y = 0; y < header[HEIGHT]; y++) {
                for (int x = 0; x < header[WIDTH]; x++) {
                    int decimal = layer1[index] & 0xff;
                    int terrain = decimal & 0b11111;
                    int overlay = decimal >> 5;

                    if (terrain < tiletypes.length) {
                        tileType = spec.getTileType("model.tile." + tiletypes[terrain]);
                    } else if (overlay == 1 || overlay == 3) {
                        tileType = spec.getTileType("model.tile.hills");
                    } else if (overlay == 5 || overlay == 7) {
                        tileType = spec.getTileType("model.tile.mountains");
                    }
                    tiles[x][y] = new Tile(game, tileType, x, y);
                    if (highestLayer == Layer.RIVERS
                        && (overlay == 2 || overlay == 3 || overlay == 6 || overlay == 7)) {
                        TileItemContainer container = new TileItemContainer(game, tiles[x][y]);
                        TileImprovement river =
                            new TileImprovement(game, tiles[x][y], riverType,
                                TileImprovementStyle.getInstance(TileImprovement.EMPTY_RIVER_STYLE));//TODO: connections!
                        river.setMagnitude(overlay <= 3 ? 1 : 2);
                        container.tryAddTileItem(river);
                        tiles[x][y].setTileItemContainer(container);
                    }
                    index++;
                }
            }
        }
        return highestLayer;
    }

    /**
     *  {@inheritDoc}
     *
     *  @return The {@code Layer} value for RIVERS
     */
    @Override
    public Layer getHighestLayer() {
        return Layer.RIVERS;
    }


}
