package electrosphere.net.server.protocol;

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

import org.joml.Vector3d;

import com.google.gson.Gson;

import electrosphere.client.entity.character.CharacterDescription;
import electrosphere.client.entity.character.ClientCharacterListDTO;
import electrosphere.engine.Globals;
import electrosphere.engine.loadingthreads.LoadingUtils;
import electrosphere.entity.Entity;
import electrosphere.entity.EntityUtils;
import electrosphere.entity.ServerEntityUtils;
import electrosphere.entity.types.creature.CreatureTemplate;
import electrosphere.entity.types.creature.CreatureToolbarData.ToolbarItem;
import electrosphere.game.data.creature.type.CreatureData;
import electrosphere.game.data.creature.type.visualattribute.VisualAttribute;
import electrosphere.net.parser.net.message.CharacterMessage;
import electrosphere.net.parser.net.message.PlayerMessage;
import electrosphere.net.server.ServerConnectionHandler;
import electrosphere.net.server.player.Player;
import electrosphere.net.template.ServerProtocolTemplate;
import electrosphere.server.character.PlayerCharacterCreation;
import electrosphere.server.datacell.Realm;
import electrosphere.server.db.DatabaseResult;
import electrosphere.server.db.DatabaseResultRow;
import electrosphere.util.Utilities;

/**
 * The server protocol for handling character messages
 */
public class CharacterProtocol implements ServerProtocolTemplate<CharacterMessage> {

    /**
     * Should spawn the existing template
     */
    public static final int SPAWN_EXISTING_TEMPLATE = -1;

    @Override
    public CharacterMessage handleAsyncMessage(ServerConnectionHandler connectionHandler, CharacterMessage message) {
        switch(message.getMessageSubtype()){
            case REQUESTCHARACTERLIST: {
                Gson gson = new Gson();
                List<CharacterDescription> characters = new LinkedList<CharacterDescription>();
                DatabaseResult result = Globals.dbController.executePreparedQuery("SELECT id, dataVal FROM charaData WHERE playerId=?;",connectionHandler.getPlayer().getDBID());
                if(result.hasResult()){
                    //if we get a valid response from the database, check that it actually matches hashes
                    for(DatabaseResultRow row : result){
                        CharacterDescription description = new CharacterDescription();
                        CreatureTemplate template = gson.fromJson(row.getAsString("dataVal"),CreatureTemplate.class);
                        description.setTemplate(template);
                        description.setId(row.getAsInteger("id") + "");
                        characters.add(description);
                    }
                }
                ClientCharacterListDTO dto = new ClientCharacterListDTO();
                dto.setCharacters(characters);
                connectionHandler.addMessagetoOutgoingQueue(CharacterMessage.constructResponseCharacterListMessage(gson.toJson(dto)));
                return null;
            }
            default: {
            } break;
        }
        return message;
    }

    @Override
    public void handleSyncMessage(ServerConnectionHandler connectionHandler, CharacterMessage message) {
        switch(message.getMessageSubtype()){
            case REQUESTCREATECHARACTER: {
                CreatureTemplate template = Utilities.deserialize(message.getdata(), CreatureTemplate.class);
                if(template != null){
                    Globals.dbController.executePreparedStatement(
                        "INSERT INTO charaData (playerId,dataVal) VALUES (?,?);",
                        connectionHandler.getPlayer().getDBID(),
                        new Gson().toJson(template)
                    );
                    connectionHandler.setCreatureTemplate(Utilities.deserialize(message.getdata(), CreatureTemplate.class));
                    connectionHandler.addMessagetoOutgoingQueue(CharacterMessage.constructResponseCreateCharacterSuccessMessage());
                } else {
                    connectionHandler.addMessagetoOutgoingQueue(CharacterMessage.constructResponseCreateCharacterFailureMessage());
                }
            } break;
            case REQUESTSPAWNCHARACTER: {
                int charaId = Integer.parseInt(message.getdata());
                CharacterProtocol.spawnEntityForClient(connectionHandler, charaId);
            } break;
            case EDITORSWAP: {
                CharacterProtocol.swapPlayerCharacter(connectionHandler);
            } break;
            case REQUESTCHARACTERLIST:
                //handled async
                break;
            case RESPONSECHARACTERLIST:
            case RESPONSECREATECHARACTERSUCCESS:
            case RESPONSECREATECHARACTERFAILURE:
            case RESPONSESPAWNCHARACTER:
                //silently ignore
                break;
        }
    }

    /**
     * Spawns the player's entity
     * @param connectionHandler The connection handler for the player
     * @param id The id in the db to lookup the template from
     * @return THe player's entity
     */
    static Entity spawnEntityForClient(ServerConnectionHandler connectionHandler, int id){
        if(id != CharacterProtocol.SPAWN_EXISTING_TEMPLATE){
            DatabaseResult result = Globals.dbController.executePreparedQuery("SELECT id, dataVal FROM charaData WHERE playerId=? AND id=?;",connectionHandler.getPlayer().getDBID(), id);
            if(result.hasResult()){
                Gson gson = new Gson();
                //if we get a valid response from the database, check that it actually matches hashes
                for(DatabaseResultRow row : result){
                    CreatureTemplate template = gson.fromJson(row.getAsString("dataVal"),CreatureTemplate.class);
                    connectionHandler.setCreatureTemplate(template);
                }
            }
        }
        Entity rVal = PlayerCharacterCreation.spawnPlayerCharacter(connectionHandler);
        Realm realm = Globals.playerManager.getPlayerRealm(connectionHandler.getPlayer());
        Vector3d spawnPoint = PlayerCharacterCreation.solveSpawnPoint(realm, connectionHandler);
        //set client initial discrete position
        connectionHandler.addMessagetoOutgoingQueue(
            PlayerMessage.constructSetInitialDiscretePositionMessage(
                realm.getServerWorldData().convertRealToChunkSpace(spawnPoint.x),
                realm.getServerWorldData().convertRealToChunkSpace(spawnPoint.y),
                realm.getServerWorldData().convertRealToChunkSpace(spawnPoint.z)
            )
        );
        return rVal;
    }

    /**
     * Swaps the type of the player character (to editor or vice-versa)
     * @param connectionHandler The connection handler to swap
     * @return The new player entity
     */
    static Entity swapPlayerCharacter(ServerConnectionHandler connectionHandler){
        //change the connection handler's creature template
        if(connectionHandler.getCurrentCreatureTemplate().getCreatureType().matches(LoadingUtils.EDITOR_RACE_NAME)){
            //solve what race to pick
            String race = LoadingUtils.EDITOR_RACE_NAME;
            List<String> races = Globals.gameConfigCurrent.getCreatureTypeLoader().getPlayableRaces();
            while(race.matches(LoadingUtils.EDITOR_RACE_NAME)){
                race = races.get(new Random().nextInt(races.size()));
            }
            //create template
            CreatureData type = Globals.gameConfigCurrent.getCreatureTypeLoader().getType(race);
            CreatureTemplate template = CreatureTemplate.create(race);
            for(VisualAttribute attribute : type.getVisualAttributes()){
                if(attribute.getType().equals(VisualAttribute.TYPE_BONE)){
                    float min = attribute.getMinValue();
                    float max = attribute.getMaxValue();
                    float defaultValue = min + (max - min)/2.0f;
                    //add attribute to creature template
                    template.putAttributeValue(attribute.getAttributeId(), defaultValue);
                } else if(attribute.getType().equals(VisualAttribute.TYPE_REMESH)){
                    template.putAttributeValue(attribute.getAttributeId(), attribute.getVariants().get(0).getId());
                }
            }
            template.getCreatureToolbarData().setSlotItem("0", new ToolbarItem(71, "terrainTool"));
            template.getCreatureToolbarData().setSlotItem("1", new ToolbarItem(72, "spawningPalette"));
            template.getCreatureToolbarData().setSlotItem("2", new ToolbarItem(73, "entityinspector"));
            template.getCreatureToolbarData().setSlotItem("3", new ToolbarItem(74, "waterSpawner"));
            //set player character template
            connectionHandler.setCreatureTemplate(template);
        } else {
            String race = LoadingUtils.EDITOR_RACE_NAME;
            CreatureData type = Globals.gameConfigCurrent.getCreatureTypeLoader().getType(race);
            CreatureTemplate template = CreatureTemplate.create(race);
            for(VisualAttribute attribute : type.getVisualAttributes()){
                if(attribute.getType().equals(VisualAttribute.TYPE_BONE)){
                    float min = attribute.getMinValue();
                    float max = attribute.getMaxValue();
                    float defaultValue = min + (max - min)/2.0f;
                    //add attribute to creature template
                    template.putAttributeValue(attribute.getAttributeId(), defaultValue);
                } else if(attribute.getType().equals(VisualAttribute.TYPE_REMESH)){
                    template.putAttributeValue(attribute.getAttributeId(), attribute.getVariants().get(0).getId());
                }
            }
            template.getCreatureToolbarData().setSlotItem("0", new ToolbarItem(71, "terrainTool"));
            template.getCreatureToolbarData().setSlotItem("1", new ToolbarItem(72, "spawningPalette"));
            template.getCreatureToolbarData().setSlotItem("2", new ToolbarItem(73, "entityinspector"));
            template.getCreatureToolbarData().setSlotItem("3", new ToolbarItem(74, "waterSpawner"));
            //set player character template
            connectionHandler.setCreatureTemplate(template);
        }

        //destroy the old entity
        Player player = connectionHandler.getPlayer();
        Entity playerEntity = player.getPlayerEntity();
        Vector3d position = EntityUtils.getPosition(playerEntity);
        Globals.realmManager.getEntityRealm(playerEntity).registerSpawnPoint(position);;
        Globals.realmManager.getEntityRealm(playerEntity).getSpawnPoint().set(position);
        ServerEntityUtils.destroyEntity(playerEntity);

        //spawn the new one
        player.setHasSentPlayerEntity(false);
        Entity newEntity = CharacterProtocol.spawnEntityForClient(connectionHandler, CharacterProtocol.SPAWN_EXISTING_TEMPLATE);
        ServerEntityUtils.repositionEntity(newEntity, position);
        return newEntity;
    }

}
