/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package logic;

import game.GameDesign;
import java.io.IOException;
import java.rmi.RemoteException;
import java.util.Timer;
import java.util.TimerTask;
import javax.microedition.lcdui.Graphics;
import javax.microedition.lcdui.game.LayerManager;
import javax.microedition.lcdui.game.Sprite;
import javax.microedition.lcdui.game.TiledLayer;
import logic.players.OwnerPlayer;
import logic.players.PhonePlayer;
import logic.players.WebPlayer;
import logic.players.Player;
import wsclient.BricksWebServiceService;
import wsclient.BricksWebServiceService_Stub;

/**
 *
 * @author avk
 */
public class BricksLayerManager extends LayerManager {

    /**
     * winner name to display in case there is no winner
     */
    public final static String WINNER_BOTH = "Friendship"; //NOI18N
    /**
     * steps in turn for each player
     */
    public final static int MAX_STEPS_IN_TURN = 5;
    /**
     * index of empty tile in tiledLayer
     */
    public final static int EMPTY_TILE_INDEX = 0;
    /**
     * moves indexes - UP
     */
    public final static int MOVE_UP = 1;
    /**
     * moves indexes - RIGTH
     */
    public final static int MOVE_RIGTH = 2;
    /**
     * moves indexes - DOWN
     */
    public final static int MOVE_DOWN = 3;
    /**
     * moves indexes - LEFT
     */
    public final static int MOVE_LEFT = 4;
    /**
     * moves indexes - NO MOVE
     */
    public final static int MOVE_NONE = 0;

    //---------------------------------------------------------
    //   dimension fields
    //  (constant after initialization)
    /**
     * The x-coordinate of the place on the game canvas where
     * the LayerManager window should appear, in terms of the
     * coordiantes of the game canvas.
     */
    static int CANVAS_X;
    /**
     * The y-coordinate of the place on the game canvas where
     * the LayerManager window should appear, in terms of the
     * coordiantes of the game canvas.
     */
    static int CANVAS_Y;
    /**
     * The width of the display window.
     */
    static int DISP_WIDTH;
    /**
     * The height of this object's visible region.
     */
    static int DISP_HEIGHT;
    /**
     * the minimum (right or left) distance the player
     * must stay away from the walls (to avoid getting
     * stuck when the sprite image changes).
     */
    static int MOVE_BUFFER = 4;
    /**
     * The width of the square tiles that this game is divided into.
     * This is the width of the stone walls as well as the duke.
     */
    static int SQUARE_WIDTH = 25; //64
    /**
     * The number of background tiles per row.
     */
    static int BACK_TILES;

    /**
     * Constructor merely sets the data.
     * @param x The x-coordinate of the place on the game canvas where
     * the LayerManager window should appear, in terms of the
     * coordiantes of the game canvas.
     * @param y The y-coordinate of the place on the game canvas where
     * the LayerManager window should appear, in terms of the
     * coordiantes of the game canvas.
     * @param width the width of the region that is to be
     * occupied by the LayoutManager.
     * @param height the height of the region that is to be
     * occupied by the LayoutManager.
     * @param customizer the object that loads the correct
     * custom data for the current platform.
     * @param canvas the MazeCanvas that this LayerManager
     * should appear on.
     */
    public BricksLayerManager(int x, int y, int width, int height, BricksGameCanvas canvas) {
        myCanvas = canvas;
        CANVAS_X = x;
        CANVAS_Y = y;
        DISP_WIDTH = width;
        DISP_HEIGHT = height;
    }

    public void init() throws IOException {
        gameDesign = new GameDesign();
        dukeSprite = gameDesign.getDukeWhite();
        jamesSprite = gameDesign.getJamesG();
        dukeSprite.defineReferencePixel(dukeSprite.getWidth() / 2, 0);
        jamesSprite.defineReferencePixel(jamesSprite.getWidth() / 2, 0);
        dukeSpriteAnimator = new SpriteAnimationTask(dukeSprite, false);
        jamesSpriteAnimator = new SpriteAnimationTask(jamesSprite, false);

        bricksLayer = gameDesign.getBricks();
        wallsLayer = gameDesign.getBorder();
        gameDesign.updateLayerManagerForLevel1(this);

        timer = new Timer();
        timer.scheduleAtFixedRate(dukeSpriteAnimator, 0, gameDesign.dukeWhiteseq001Delay);
        timer.scheduleAtFixedRate(jamesSpriteAnimator, 0, gameDesign.JamesGseq001Delay);

        myBricksLeft = calculateBricks(bricksLayer);

        initPlayers();
        recalculateShownArea();
        
        System.gc();
    }

 private BricksWebServiceService client = null;
    public BricksWebServiceService getMobileClient() {
        if (client == null) {
            client = new BricksWebServiceService_Stub();
        }
        return client;
    }

    private void initPlayers() {
        myGameInfo = new GameInfoImpl();

//#if ONLINE == "true"
        // TODO switch to waiting screen and move communication to another thread
        try {
            String id = getMobileClient().initPlayer("");

            int order = 0;
            while ((order = getMobileClient().startGame(id)) == 0){
                myWaitingForOpponent = true;
                Thread.sleep(500);
            }
            myWaitingForOpponent = false;

            if (order == 1) {
                myPlayer1 = new OwnerPlayer(dukeSprite, dukeSpriteAnimator, myGameInfo);
                myPlayer2 = new WebPlayer(jamesSprite, jamesSpriteAnimator, myGameInfo,
                        getMobileClient(), id);
            } else {
                myPlayer1 = new WebPlayer(dukeSprite, dukeSpriteAnimator, myGameInfo,
                        getMobileClient(), id);
                myPlayer2 = new OwnerPlayer(jamesSprite, jamesSpriteAnimator, myGameInfo);
            }
        } catch (RemoteException ex) {
            myCanvas.errorMsg(ex);
        } catch (InterruptedException ex) {
            myCanvas.errorMsg(ex);
        }
//#else
//#         // init player 1
//#         myPlayer1 = new OwnerPlayer(dukeSprite, dukeSpriteAnimator, myGameInfo);
//#         // init player 2
//#         myPlayer2 = new PhonePlayer(jamesSprite, jamesSpriteAnimator, myGameInfo);
//#endif

        myActivePlayer = myPlayer1;
    }

    public void reset() {
        remove(jamesSprite);
        remove(dukeSprite);
        remove(bricksLayer);
        remove(wallsLayer);
        dukeSpriteAnimator.cancel();//stop the duke animator
        jamesSpriteAnimator.cancel();//stop the duke animator

        System.gc();
    }

    public void requestMove(int keyState) {
        // get active player
        if (!haveRemainingSteps()) {
            switchActivePlayer();
            recalculateShownArea();
        }
        if (!myActivePlayer.isReadyToMove()){
            myWaitingForOpponent = true;
            return;
        }
        myWaitingForOpponent = false;

        // give keystate to the player. get move from the player
        int move = myActivePlayer.move(keyState);
        if (move == MOVE_NONE){
            return;
        }

        // remember step
        addStepToHistory(move);

        Sprite sprite = myActivePlayer.getSprite();

        int dx = (move == MOVE_LEFT) ? -SQUARE_WIDTH
                : (move == MOVE_RIGTH) ? SQUARE_WIDTH : 0;
        int dy = (move == MOVE_UP) ? -SQUARE_WIDTH
                : (move == MOVE_DOWN) ? SQUARE_WIDTH : 0;

        sprite.move(dx, dy);

        if (canNotGo(sprite)) {
            move = MOVE_NONE;
            sprite.move(-dx, -dy);
        } else {
            myStepsLeft--;

            moveShownArea(dx, dy);
            if (hasGotBrick(sprite)) {
                int col = sprite.getRefPixelX() / SQUARE_WIDTH;
                int row = sprite.getRefPixelY() / SQUARE_WIDTH;
                bricksLayer.setCell(col, row, EMPTY_TILE_INDEX);
                myActivePlayer.takeBrick();
                myBricksLeft--;
                if (noMoreBricks()) {
                    setGameOver();
                }
            }
        }

        updateSprite(myActivePlayer, keyState);
    }

    private String moveAsString(int move){
        if (move == MOVE_LEFT){
            return "MOVE_LEFT";
        }
        if (move == MOVE_RIGTH){
            return "MOVE_RIGTH";
        }
        if (move == MOVE_UP){
            return "MOVE_UP";
        }
        if (move == MOVE_DOWN){
            return "MOVE_DOWN";
        }
        return "MOVE_NONE";
    }

    /**
     * paint the game graphic on the screen.
     */
    public void paint(Graphics g) throws Exception {
        g.setColor(BricksGameCanvas.WHITE);
        // paint the background white to cover old game objects
        // that have changed position since last paint.
        // here coordinates are given
        // with respect to the graphics (canvas) origin:
        g.fillRect(0, 0, DISP_WIDTH, DISP_HEIGHT);
        // here coordinates are given
        // with respect to the LayerManager origin:
        setViewWindow(myViewWindowX, myViewWindowY, DISP_WIDTH, DISP_HEIGHT);
        // call the paint funstion of the superclass LayerManager
        // to paint all of the Layers
        paint(g, CANVAS_X, CANVAS_Y);
    }

    public boolean isWaitingForOpponent(){
        return myWaitingForOpponent;
    }

    public String getActivePlayerName(){
        if (myActivePlayer != null){
            return myActivePlayer.getName();
        } else {
            return null;
        }
    }

    public int getRemainingStepsCount(){
        return myStepsLeft;
    }

    public int getPlayer1Bricks(){
        return getPlayerBricks(myPlayer1);
    }

    public int getPlayer2Bricks(){
        return getPlayerBricks(myPlayer2);
    }

    private int getPlayerBricks(Player player){
        return player != null ? player.getBricks() : 0;
    }

    public int getBricksLeft(){
        return myBricksLeft;
    }

    private int calculateBricks(TiledLayer bricksLayer) {
        int cnt = 0;
        for (int i = 0; i < bricksLayer.getColumns(); i++) {
            for (int j = 0; j < bricksLayer.getRows(); j++) {
                if (bricksLayer.getCell(i, j) != EMPTY_TILE_INDEX) {
                    cnt++;
                }
            }
        }
        return cnt;
    }

    private boolean hasGotBrick(Sprite sprite) {
        return sprite.collidesWith(bricksLayer, true);
    }

    private boolean haveRemainingSteps() {
        return myStepsLeft > 0;
    }

    private void addStepToHistory(int move){
        myStepsHistory[MAX_STEPS_IN_TURN - myStepsLeft] = move;
    }

    private void moveShownArea(int dx, int dy) {
            myViewWindowX += dx;
            myViewWindowY += dy;
            normalizeShownArea();
    }

    private void normalizeShownArea() {
        // x left
        if (myViewWindowX < 0){
            myViewWindowX = 0;
        }
        // x right
        int maxX = wallsLayer.getColumns() * wallsLayer.getCellWidth() - DISP_WIDTH;
        if (myViewWindowX > maxX){
            myViewWindowX = maxX;
        }
        // y top
        if (myViewWindowY < -BricksGameCanvas.PLAYERS_INFO_BLOCK_HEIGHT){
            myViewWindowY = -BricksGameCanvas.PLAYERS_INFO_BLOCK_HEIGHT;
        }
        // y bottom
        int maxY = wallsLayer.getRows() * wallsLayer.getCellHeight() 
                + BricksGameCanvas.PLAYERS_INFO_BLOCK_HEIGHT - DISP_HEIGHT;
        if (myViewWindowY > maxY){
            myViewWindowY = maxY;
        }
    }

    private void recalculateShownArea() {
        // this sets the view screen so that the player is in the center.
        myViewWindowX = myActivePlayer.getSprite().getX() - ((DISP_WIDTH - SQUARE_WIDTH) / 2);
        myViewWindowY = myActivePlayer.getSprite().getY() - ((DISP_HEIGHT - SQUARE_WIDTH) / 2);
        normalizeShownArea();
    }
    
    private boolean canNotGo(Sprite sprite) {
        boolean opponent = sprite.collidesWith(
                sprite == this.dukeSprite ? this.jamesSprite : this.dukeSprite, true);
        boolean walls = sprite.collidesWith(wallsLayer, true);
        return opponent || walls;
        /*
        return sprite.collidesWith(
            sprite == this.dukeSprite ? this.jamesSprite : this.dukeSprite, true)
            || sprite.getX() < 0 || sprite.getY() < 0
            || sprite.getX() > (DISP_WIDTH - sprite.getWidth())
            || sprite.getY() > (DISP_HEIGHT - sprite.getHeight()
            );
         */
    }

    private boolean noMoreBricks() {
        return myBricksLeft <= 0;
    }

    private void setGameOver() {
        String winnerName = WINNER_BOTH;
        if (myPlayer1.getBricks() > myPlayer2.getBricks()){
            winnerName = myPlayer1.getName();
        } else if (myPlayer1.getBricks() < myPlayer2.getBricks()){
            winnerName = myPlayer2.getName();
        }
        // TODO post game over to web service
        myCanvas.setGameOver(winnerName);
    }

    private void switchActivePlayer() {
        myActivePlayer.getAnimator().stop();

        myStepsLeft = MAX_STEPS_IN_TURN;
        if (myActivePlayer.equals(myPlayer1)) {
            myActivePlayer = myPlayer2;
        } else {
            myActivePlayer = myPlayer1;
        }

        myGameInfo.setStepsHistory(myStepsHistory);
        myStepsHistory = new int[MAX_STEPS_IN_TURN];

        myActivePlayer.getAnimator().move();
    }

    private void updateSprite(Player player, int direction) {
        if (direction == MOVE_RIGTH || direction == MOVE_DOWN) {
            player.getSprite().setTransform(Sprite.TRANS_MIRROR);
        } else if (direction == MOVE_UP || direction == MOVE_LEFT) {
            player.getSprite().setTransform(Sprite.TRANS_MIRROR);
        }
    }

    /**
     * Animates a sprite.
     */
    private class SpriteAnimationTask extends TimerTask implements Player.SpriteAnimator {

        private boolean moving = false;
        private Sprite sprite;

        public SpriteAnimationTask(Sprite sprite, boolean forward) {
            this.sprite = sprite;
        }

        public void run() {
            if (!this.moving) {
                return;
            }
            this.sprite.nextFrame();
        }

        public void move() {
            this.moving = true;
        }

        public void stop() {
            this.moving = false;
        }
    }

    private class GameInfoImpl implements GameInfo {

        private int[] mySteps;

        public TiledLayer getBricksLayer() {
            return BricksLayerManager.this.bricksLayer;
        }

        public TiledLayer getWallsLayer() {
            return BricksLayerManager.this.wallsLayer;
        }

        public Sprite getOpponentSprite(Sprite sprite) {
            return sprite == BricksLayerManager.this.dukeSprite
                    ? BricksLayerManager.this.jamesSprite
                    : BricksLayerManager.this.dukeSprite;
        }

        public int[] getStepsHistory() {
            return mySteps;
        }

        protected void setStepsHistory(int[] steps) {
            mySteps = steps;
        }

    }

    private Player myActivePlayer;
    private Player myPlayer1;
    private Player myPlayer2;
    private int myStepsLeft = MAX_STEPS_IN_TURN;
    private int myBricksLeft;
    private boolean myWaitingForOpponent = false;

    /**
     * Current game details provider
     */
    private GameInfoImpl myGameInfo;
    /**
     * the game design.
     */
    private GameDesign gameDesign;
    /**
     * the canvas.
     */
    private BricksGameCanvas myCanvas;
    /**
     * The bricks
     */
    private TiledLayer bricksLayer;
    /**
     * The walls
     */
    private TiledLayer wallsLayer;
    /**
     * Duke sprite
     */
    private Sprite dukeSprite;
    /**
     * James sprite
     */
    private Sprite jamesSprite;
    /**
     * steps made by player. to be passed to his opponent
     */
    private int[] myStepsHistory = new int[MAX_STEPS_IN_TURN];
    /**
     *  animate the duke sprite
     */
    private SpriteAnimationTask dukeSpriteAnimator;
    /**
     *  animate the james sprite
     */
    private SpriteAnimationTask jamesSpriteAnimator;
    private Timer timer;
    /**
     * The leftmost x-coordinate that should be visible on the
     * screen in terms of this objects internal coordinates.
     */
    private int myViewWindowX;
    /**
     * The top y-coordinate that should be visible on the
     * screen in terms of this objects internal coordinates.
     */
    private int myViewWindowY;
}
