package electrosphere.game.data;

import java.util.LinkedList;
import java.util.List;

import electrosphere.game.data.audio.SurfaceAudioCollection;
import electrosphere.game.data.creature.type.CreatureData;
import electrosphere.game.data.creature.type.CreatureTypeLoader;
import electrosphere.game.data.creature.type.attack.AttackMoveResolver;
import electrosphere.game.data.creature.type.model.CreatureTypeMap;
import electrosphere.game.data.foliage.type.model.FoliageTypeMap;
import electrosphere.game.data.item.type.model.ItemTypeMap;
import electrosphere.game.data.object.type.ObjectData;
import electrosphere.game.data.object.type.model.ObjectTypeLoader;
import electrosphere.game.data.object.type.model.ObjectTypeMap;
import electrosphere.game.data.projectile.ProjectileTypeHolder;
import electrosphere.game.data.structure.type.model.StructureTypeMap;
import electrosphere.game.data.tutorial.HintDefinition;
import electrosphere.game.data.voxel.VoxelData;
import electrosphere.game.server.race.model.RaceMap;
import electrosphere.game.server.symbolism.model.SymbolMap;
import electrosphere.util.FileUtils;

/**
 * Current configuration for the data of the game
 */
public class Config {
    
    CreatureTypeLoader creatureTypeLoader;
    StructureTypeMap structureTypeMap;
    ItemTypeMap itemMap;
    FoliageTypeMap foliageMap;
    ObjectTypeLoader objectTypeLoader;
    SymbolMap symbolMap;
    RaceMap raceMap;
    ProjectileTypeHolder projectileTypeHolder;

    //data about every voxel type
    VoxelData voxelData;

    //the hints that are defined
    HintDefinition hintData;

    /**
     * The surface audio definitions
     */
    SurfaceAudioCollection surfaceAudioCollection;
    
    /**
     * Loads the default data
     * @return The config
     */
    public static Config loadDefaultConfig(){
        Config config = new Config();
        config.creatureTypeLoader = loadCreatureTypes("Data/creatures.json");
        config.itemMap = FileUtils.loadObjectFromAssetPath("Data/items.json", ItemTypeMap.class);
        config.structureTypeMap = FileUtils.loadObjectFromAssetPath("Data/structures.json", StructureTypeMap.class);
        config.foliageMap = FileUtils.loadObjectFromAssetPath("Data/foliage.json", FoliageTypeMap.class);
        config.objectTypeLoader = loadObjectTypes("Data/objects.json");
        config.symbolMap = FileUtils.loadObjectFromAssetPath("Data/symbolism.json", SymbolMap.class);
        config.raceMap = FileUtils.loadObjectFromAssetPath("Data/races.json", RaceMap.class);
        config.voxelData = FileUtils.loadObjectFromAssetPath("Data/voxelTypes.json", VoxelData.class);
        config.projectileTypeHolder = FileUtils.loadObjectFromAssetPath("Data/projectile.json", ProjectileTypeHolder.class);
        config.hintData = FileUtils.loadObjectFromAssetPath("Data/tutorial/hints.json", HintDefinition.class);
        config.surfaceAudioCollection = FileUtils.loadObjectFromAssetPath("Data/audio/surface.json", SurfaceAudioCollection.class);
        config.projectileTypeHolder.init();

        //validate
        ConfigValidator.valdiate(config);

        
        return config;
    }

    /**
     * Reads a child creature defintion file
     * @param filename The filename
     * @return The list of creatures in the file
     */
    static List<CreatureData> readCreatureTypeFile(String filename){
        List<CreatureData> typeList = new LinkedList<CreatureData>();
        CreatureTypeMap typeMap = FileUtils.loadObjectFromAssetPath(filename, CreatureTypeMap.class);
        //push the types from this file
        for(CreatureData type : typeMap.getCreatures()){
            typeList.add(type);
        }
        //push types from any other files
        for(String filepath : typeMap.getFiles()){
            List<CreatureData> parsedTypeList = readCreatureTypeFile(filepath);
            for(CreatureData type : parsedTypeList){
                typeList.add(type);
            }
        }
        return typeList;
    }

    /**
     * Loads all creature definition files recursively
     * @param initialPath The initial path to recurse from
     * @return The creature defintion interface
     */
    static CreatureTypeLoader loadCreatureTypes(String initialPath) {
        CreatureTypeLoader loader = new CreatureTypeLoader();
        List<CreatureData> typeList = readCreatureTypeFile(initialPath);
        for(CreatureData type : typeList){
            if(type.getAttackMoves() != null){
                type.setAttackMoveResolver(new AttackMoveResolver(type.getAttackMoves()));
            }
            loader.putCreature(type.getCreatureId(), type);
            //loop through all creatures and add ones with PLAYABLE token to list of playable races
            for(String token : type.getTokens()){
                if(token.contains("PLAYABLE")){
                    loader.putPlayableRace(type.getCreatureId());
                }
            }
        }
        return loader;
    }

    /**
     * Read a file that define object entity types
     * @param filename The filename to read
     * @return The list of object definitions
     */
    static List<ObjectData> readObjectTypeFile(String filename){
        List<ObjectData> typeList = new LinkedList<ObjectData>();
        ObjectTypeMap typeMap = FileUtils.loadObjectFromAssetPath(filename, ObjectTypeMap.class);
        //push the types from this file
        for(ObjectData type : typeMap.getObjects()){
            typeList.add(type);
        }
        //push types from any other files
        for(String filepath : typeMap.getFiles()){
            List<ObjectData> parsedTypeList = readObjectTypeFile(filepath);
            for(ObjectData type : parsedTypeList){
                typeList.add(type);
            }
        }
        return typeList;
    }

    /**
     * Recursively reads files that define object entities
     * @param initialPath The initial path to start recursing from
     * @return The object entity interface
     */
    static ObjectTypeLoader loadObjectTypes(String initialPath) {
        ObjectTypeLoader loader = new ObjectTypeLoader();
        List<ObjectData> typeList = readObjectTypeFile(initialPath);
        for(ObjectData type : typeList){
            loader.putObject(type.getObjectId(), type);
        }
        return loader;
    }

    /**
     * Gets the interface for creature definitions loaded into memory
     * @return The interface
     */
    public CreatureTypeLoader getCreatureTypeLoader() {
        return creatureTypeLoader;
    }

    /**
     * Gets the data on all structure entities in memory
     * @return The structure definitions
     */
    public StructureTypeMap getStructureTypeMap() {
        return structureTypeMap;
    }

    /**
     * Gets the data on all item types in memory
     * @return the data on all items
     */
    public ItemTypeMap getItemMap() {
        return itemMap;
    }

    /**
     * Gets the data on all foliage types in memory
     * @return The foliage data
     */
    public FoliageTypeMap getFoliageMap() {
        return foliageMap;
    }
    
    /**
     * Gets the symbolism map
     * @return The symbolism map
     */
    public SymbolMap getSymbolMap() {
        return symbolMap;
    }
    
    /**
     * Gets the data that defines all creature races in the engine
     * @return The data on all races
     */
    public RaceMap getRaceMap() {
        return raceMap;
    }

    /**
     * Gets the definitions of all object entities in memory
     * @return The objects
     */
    public ObjectTypeLoader getObjectTypeLoader() {
        return objectTypeLoader;
    }

    /**
     * Gets the definitions all projectiles
     * @return the projectile data
     */
    public ProjectileTypeHolder getProjectileMap(){
        return projectileTypeHolder;
    }

    /**
     * Gets the voxel data
     * @return The voxel data
     */
    public VoxelData getVoxelData(){
        return voxelData;
    }

    /**
     * The tutorial hints data
     * @return The hints
     */
    public HintDefinition getHintData(){
        return hintData;
    }

    /**
     * Gets the surface audio collection
     * @return The surface audio collection
     */
    public SurfaceAudioCollection getSurfaceAudioCollection(){
        return this.surfaceAudioCollection;
    }
    
}
