// Author(s): _____

import java.util.Arrays;

public class HeatModelThreaded implements HeatModel {
    private final int width;
    private final int height;
    private final double conductivity;

    private double[][] heat;
    private double[][] next;
    
    private int pinnedX;
    private int pinnedY;
    private double pinnedHeat;

    /** Constructs a grid with 'height' rows and 'width' columns, whose initial heat values
     * are all 'initHeat,' using 'conductivity' as the update multiplier. */
    public HeatModelThreaded(int width, int height, double initHeat,
            double conductivity, int subgridWidth, int subgridHeight) {
        this.width = width;
        this.height = height;
        this.conductivity = conductivity;

        heat = new double[height][width];
        next = new double[height][width];
        for (int y = 0; y < width; y++) {
            Arrays.fill(heat[y], initHeat);
        }
        
        pinnedX = -1;
    }

    /** Sets the value at (x,y) to 'heat' where future steps do not alter this value at (x,y).
     * This replaces any previously pinned value, so that the previously pinned location will change
     * with future steps. */
    public void setPinned(int x, int y, double heat) {
        if (x >= 0 && y >= 0 && x < width && y < height) {
            pinnedX = x;
            pinnedY = y;
            pinnedHeat = heat;
        } else {
            pinnedX = -1;
        }
    }

    /** Clears any previously pinned value, so that updates now alter that location's value. */
    public void clearPinned() {
        pinnedX = -1;
    }

    /** Copies all current heat values into the 'result' array. */
    public void getAll(double[][] result) {
        double[][] heat = this.heat;
        for (int i = 0; i < heat.length; i++) {
            System.arraycopy(heat[i], 0, result[i], 0, heat[i].length);
        }
    }

    /** Steps the simulation forward one time step. */
    public void step() {
        // Load instance variables into local variables
        int width = this.width;
        int height = this.height;
        double conduct = this.conductivity;
        double[][] heat = this.heat;
        double[][] next = this.next;
        
        // Iterate through each (x,y)
        for (int y = 0; y < height; y++) {
            int yPrev = y == 0 ? height - 1 : y - 1;
            int yNext = y == height - 1 ? 0 : y + 1;
            int xPrev = width - 1;
            for (int x = 0; x < width; x++) {
                int xNext = x == width - 1 ? 0 : x + 1;

                double heat_xy = heat[y][x];
                double heat_neighbors = heat[yPrev][x] + heat[yNext][x]
                        + heat[y][xPrev] + heat[y][xNext];
                double laplacian = heat_neighbors - 4 * heat_xy;
                next[y][x] = heat_xy + conduct * laplacian;
                
                xPrev = x;
            }
        }
        
        int pinnedX = this.pinnedX;
        if (pinnedX >= 0) {
            next[this.pinnedY][pinnedX] = this.pinnedHeat;
        }
        
        this.next = heat;
        this.heat = next;
    }
    
    /** Retires any resources (e.g., threads) created by this model. */
    public void retire() { }
}
