package com.macilias.games.controller;

import com.macilias.games.model.Field;

import java.util.Random;

/**
 * Created by mac on 20.07.15.
 */
public class GameImpl implements Game {

    private static GameImpl instance;
    private Field field;
    private boolean isOver = false;
    private Random random = new Random(467462421234L);
    private long score = 0L;

    private GameImpl() {
        try {
            field = new Field();
        } catch (Exception e) {
            // should never ever happen
            e.printStackTrace();
        }
    }

    private GameImpl(int size) throws Exception {
        field = new Field(size);
    }

    public static GameImpl getInstance() {
        if (instance == null) {
            instance = new GameImpl();
        }
        return instance;
    }

    public static GameImpl getInstance(int size) throws Exception {
        if (instance == null) {
            instance = new GameImpl(size);
        }
        return instance;
    }

    @Override
    public Field moveLeft() {
        resetField();
        for (int i = 0; i < field.field.length; i++) {
            field.field[i] = reverse(manipulateRow(reverse(field.field[i])));
        }
        return returnField();
    }

    @Override
    public Field moveRight() {
        resetField();
        for (int i = 0; i < field.field.length; i++) {
            field.field[i] = manipulateRow(field.field[i]);
        }
        return returnField();
    }

    @Override
    public Field moveUp() {
        resetField();
        int[][] temp = transpose(field.field);
        for (int i = 0; i < temp.length; i++) {
            temp[i] = reverse(manipulateRow(reverse(temp[i])));
        }
        field.field = transpose(temp);
        return returnField();
    }

    @Override
    public Field moveDown() {
        resetField();
        int[][] temp = transpose(field.field);
        for (int i = 0; i < temp.length; i++) {
            temp[i] = manipulateRow(temp[i]);
        }
        field.field = transpose(temp);
        return returnField();
    }


    private void resetField() {
        field.setChanged(false);
    }

    private Field returnField() {
        if (field.isChanged()) {
            addRandom();
        }
        return field;
    }

    @Override
    public Field getField() {
        addRandom();
        return field;
    }

    @Override
    public boolean isOver() {
        return isOver;
    }

    @Override
    public long getScore() {
        return score;
    }

    @VisibleForTesting
    public int[] manipulateRow(int[] row) {

        int collisionAt = 0;
        for (int i = row.length - 2; i >= 0; i--) {
            boolean collated = false;
            boolean stopped = false;
            int current = row[i];
            if (current != 0) {
                for (int j = i; j < row.length - 1 && !collated && !stopped; j++) {
                    int next = row[j + 1];
                    if (current == next && collisionAt != j + 1) {
                        collated = true;
                        collisionAt = j + 1;
                        int tile = current * 2;
                        row[j + 1] = tile;
                        row[j] = 0;
                        score += tile;
                        field.setChanged(true);
                    } else if (next != 0) {
                        stopped = true;
                    } else {
                        row[j + 1] = current;
                        row[j] = 0;
                        field.setChanged(true);
                    }
                }
            }
        }
        return row;
    }

    private int[] reverse(int[] row) {
        for (int i = 0; i < row.length / 2; i++) {
            int temp = row[i];
            row[i] = row[row.length - i - 1];
            row[row.length - i - 1] = temp;
        }
        return row;
    }

    private int[][] transpose(int[][] field) {
        int[][] result = new int[field.length][field[0].length];
        for (int i = 0; i < field[0].length; i++) {
            for (int j = 0; j < field.length; j++) {
                result[i][j] = field[j][i];
            }
        }
        return result;
    }

    private void addRandom() {
        if (slotsOpen()) {
            boolean inserted = false;
            while (!inserted) {
                int rx = random.nextInt(field.field.length);
                int ry = random.nextInt(field.field.length);
                if (field.field[rx][ry] == 0) {
                    int rv = random.nextInt(9) == 0 ? 4 : 2;
                    field.field[rx][ry] = rv;
                    inserted = true;
                }
            }
        }
    }

    private boolean slotsOpen() {
        for (int i = 0; i < field.field.length; i++) {
            for (int j = 0; j < field.field.length; j++) {
                if (field.field[i][j] == 0) {
                    return true;
                }
            }
        }
        isOver = true;
        return false;
    }
}

@@ -2201,16 +2201,31 @@
 hanged()
+ && slotsOpen()
 ) %7B%0A    
@@ -4240,39 +4240,8 @@
 ) %7B%0A
-        if (slotsOpen()) %7B%0A    
     
@@ -4278,20 +4278,16 @@
         
-    
 while (!
@@ -4294,28 +4294,24 @@
 inserted) %7B%0A
-    
             
@@ -4363,28 +4363,24 @@
             
-    
 int ry = ran
@@ -4416,36 +4416,32 @@
 h);%0A            
-    
 if (field.field%5B
@@ -4468,28 +4468,24 @@
             
-  
-  
 int rv = ran
@@ -4521,36 +4521,32 @@
                 
-    
 field.field%5Brx%5D%5B
@@ -4571,20 +4571,16 @@
         
-   
- 
 inserted
@@ -4588,34 +4588,16 @@
 = true;%0A
-                %7D%0A
         

fix 1 slotsOpen() has not been called when field was unchanged