////////////////////////////////////////
// Programming project code
// UniBw M, 2022, 2023, 2024
// www.unibw.de/inf2
// (c) Mark Minas (mark.minas@unibw.de)
////////////////////////////////////////

package pp.monopoly.game.client;

import pp.monopoly.message.client.ClientMessage;
import pp.monopoly.message.server.BuyPropertyResponse;
import pp.monopoly.message.server.DiceResult;
import pp.monopoly.message.server.EventDrawCard;
import pp.monopoly.message.server.GameOver;
import pp.monopoly.message.server.GameStart;
import pp.monopoly.message.server.JailEvent;
import pp.monopoly.message.server.PlayerStatusUpdate;
import pp.monopoly.message.server.ServerInterpreter;
import pp.monopoly.message.server.TimeOutWarning;
import pp.monopoly.message.server.TradeReply;
import pp.monopoly.message.server.TradeRequest;
import pp.monopoly.message.server.UpdatePlayerAssets;
import pp.monopoly.message.server.ViewAssetsResponse;
import pp.monopoly.model.IntPoint;
import pp.monopoly.model.Board;
import pp.monopoly.notification.ClientStateEvent;
import pp.monopoly.notification.GameEvent;
import pp.monopoly.notification.GameEventBroker;
import pp.monopoly.notification.GameEventListener;
import pp.monopoly.notification.InfoTextEvent;
import pp.monopoly.notification.Sound;
import pp.monopoly.notification.SoundEvent;

import java.io.File;
import java.io.IOException;
import java.lang.System.Logger;
import java.lang.System.Logger.Level;
import java.util.ArrayList;
import java.util.List;

import pp.monopoly.message.client.ClientMessage;
import pp.monopoly.message.server.ServerInterpreter;
import pp.monopoly.model.Board;
import pp.monopoly.model.IntPoint;
import pp.monopoly.notification.ClientStateEvent;
import pp.monopoly.notification.GameEvent;
import pp.monopoly.notification.GameEventBroker;
import pp.monopoly.notification.GameEventListener;
import pp.monopoly.notification.InfoTextEvent;
import pp.monopoly.notification.Sound;
import pp.monopoly.notification.SoundEvent;

/**
 * Controls the client-side game logic for Monopoly.
 * Manages the player's placement, interactions with the map, and response to server messages.
 */
public class ClientGameLogic implements ServerInterpreter, GameEventBroker {
    static final Logger LOGGER = System.getLogger(ClientGameLogic.class.getName());
    private final ClientSender clientSender;
    private final List<GameEventListener> listeners = new ArrayList<>();
    private Board ownMap;
    private Board harbor;
    private Board opponentMap;
    private ClientState state = new ClientState(this) {
        
    };

    /**
     * Constructs a ClientGameLogic with the specified sender object.
     *
     * @param clientSender the object used to send messages to the server
     */
    public ClientGameLogic(ClientSender clientSender) {
        this.clientSender = clientSender;
    }

    /**
     * Returns the current state of the game logic.
     */
    ClientState getState() {
        return state;
    }

    /**
     * Sets the current state of the game logic.
     *
     * @param newState the new state to be set
     */
    void setState(ClientState newState) {
        LOGGER.log(Level.DEBUG, "state transition {0} --> {1}", state.getName(), newState.getName()); //NON-NLS
        state = newState;
        notifyListeners(new ClientStateEvent());
        state.entry();
    }

    /**
     * Returns the player's own map.
     *
     * @return the player's own map
     */
    public Board getMap() {
        return ownMap;
    }

    /**
     * Moves the preview figure to the specified position.
     *
     * @param pos the new position for the preview figure
     */
    public void movePreview(IntPoint pos) {
        state.movePreview(pos);
    }

    /**
     * Sets the informational text to be displayed to the player.
     *
     * @param key the key for the info text
     */
    void setInfoText(String key) {
        notifyListeners(new InfoTextEvent(key));
    }

    /**
     * Emits an event to play the specified sound.
     *
     * @param sound the sound to be played.
     */
    public void playSound(Sound sound) {
        notifyListeners(new SoundEvent(sound));
    }

    /**
     * Loads a map from the specified file.
     *
     * @param file the file to load the map from
     * @throws IOException if an I/O error occurs
     */
    public void loadMap(File file) throws IOException {
        state.loadMap(file);
    }

    /**
     * Sends a message to the server.
     *
     * @param msg the message to be sent
     */
    void send(ClientMessage msg) {
        if (clientSender == null)
            LOGGER.log(Level.ERROR, "trying to send {0} with sender==null", msg); //NON-NLS
        else
            clientSender.send(msg);
    }

    /**
     * Adds a listener to receive game events.
     *
     * @param listener the listener to add
     */
    public synchronized void addListener(GameEventListener listener) {
        listeners.add(listener);
    }

    /**
     * Removes a listener from receiving game events.
     *
     * @param listener the listener to remove
     */
    public synchronized void removeListener(GameEventListener listener) {
        listeners.remove(listener);
    }

    /**
     * Notifies all listeners of a game event.
     *
     * @param event the game event to notify listeners of
     */
    @Override
    public void notifyListeners(GameEvent event) {
        final List<GameEventListener> copy;
        synchronized (this) {
            copy = new ArrayList<>(listeners);
        }
        for (GameEventListener listener : copy)
            event.notifyListener(listener);
    }

    /**
     * Called once per frame by the update loop.
     *
     * @param delta time in seconds since the last update call
     */
    public void update(float delta) {
        state.update(delta);
    }

    @Override
    public void received(BuyPropertyResponse msg) {
        // TODO Auto-generated method stub
        throw new UnsupportedOperationException("Unimplemented method 'received'");
    }

    @Override
    public void received(DiceResult msg) {
        // TODO Auto-generated method stub
        throw new UnsupportedOperationException("Unimplemented method 'received'");
    }

    @Override
    public void received(EventDrawCard msg) {
        // TODO Auto-generated method stub
        throw new UnsupportedOperationException("Unimplemented method 'received'");
    }

    @Override
    public void received(GameOver msg) {
        // TODO Auto-generated method stub
        throw new UnsupportedOperationException("Unimplemented method 'received'");
    }

    @Override
    public void received(GameStart msg) {
        // TODO Auto-generated method stub
        throw new UnsupportedOperationException("Unimplemented method 'received'");
    }

    @Override
    public void received(JailEvent msg) {
        // TODO Auto-generated method stub
        throw new UnsupportedOperationException("Unimplemented method 'received'");
    }

    @Override
    public void received(PlayerStatusUpdate msg) {
        // TODO Auto-generated method stub
        throw new UnsupportedOperationException("Unimplemented method 'received'");
    }

    @Override
    public void received(TimeOutWarning msg) {
        // TODO Auto-generated method stub
        throw new UnsupportedOperationException("Unimplemented method 'received'");
    }

    @Override
    public void received(UpdatePlayerAssets msg) {
        // TODO Auto-generated method stub
        throw new UnsupportedOperationException("Unimplemented method 'received'");
    }

    @Override
    public void received(ViewAssetsResponse msg) {
        // TODO Auto-generated method stub
        throw new UnsupportedOperationException("Unimplemented method 'received'");
    }

    @Override
    public void received(TradeReply msg) {
        // TODO Auto-generated method stub
        throw new UnsupportedOperationException("Unimplemented method 'received'");
    }

    @Override
    public void received(TradeRequest msg) {
        // TODO Auto-generated method stub
        throw new UnsupportedOperationException("Unimplemented method 'received'");
    }
}
