package mindustry.world;

import arc.func.*;
import arc.math.*;
import arc.math.geom.*;
import arc.util.*;
import mindustry.gen.*;

import java.util.*;

/** A tile container. */
public class Tiles implements Iterable<Tile>{
    public final int width, height;

    final Tile[] array;
    final Puddle[] puddles;
    final Fire[] fires;

    @Nullable long[] tmpFloorState, tmpBlockState;

    public Tiles(int width, int height){
        this.array = new Tile[width * height];
        this.width = width;
        this.height = height;
        this.puddles = new Puddle[width * height];
        this.fires = new Fire[width * height];
    }

    public long getTmpFloorState(int pos){
        return tmpFloorState == null ? 0 : tmpFloorState[pos];
    }

    public void setTmpFloorState(int pos, long value){
        if(tmpFloorState == null || tmpFloorState.length != array.length) tmpFloorState = new long[array.length];
        tmpFloorState[pos] = value;
    }

    public long getTmpBlockState(int pos){
        return tmpBlockState == null ? 0 : tmpBlockState[pos];
    }

    public void setTmpBlockState(int pos, long value){
        if(tmpBlockState == null || tmpBlockState.length != array.length) tmpBlockState = new long[array.length];
        tmpBlockState[pos] = value;
    }

    public Puddle getPuddle(int pos){
        return puddles[pos];
    }

    public void setPuddle(int pos, Puddle p){
        puddles[pos] = p;
    }

    public @Nullable Fire getFire(int pos){
        return fires[pos];
    }

    public void setFire(int pos, Fire f){
        fires[pos] = f;
    }

    public void each(Intc2 cons){
        for(int x = 0; x < width; x++){
            for(int y = 0; y < height; y++){
                cons.get(x, y);
            }
        }
    }

    /** fills this tile set with empty air tiles. */
    public void fill(){
        for(int i = 0; i < array.length; i++){
            array[i] = new Tile(i % width, i / width);
        }
    }

    /** set a tile at a position; does not range-check. use with caution. */
    public void set(int x, int y, Tile tile){
        array[y*width + x] = tile;
    }

    /** set a tile at a raw array position; used for fast iteration / 1-D for-loops */
    public void seti(int i, Tile tile){
        array[i] = tile;
    }

    /** @return whether these coordinates are in bounds */
    public boolean in(int x, int y){
        return x >= 0 && x < width && y >= 0 && y < height;
    }

    /** @return a tile at coordinates, or null if out of bounds */
    @Nullable
    public Tile get(int x, int y){
        return (x < 0 || x >= width || y < 0 || y >= height) ? null : array[y*width + x];
    }

    /** @return a tile at coordinates; throws an exception if out of bounds */
    public Tile getn(int x, int y){
        if(x < 0 || x >= width || y < 0 || y >= height) throw new IllegalArgumentException(x + ", " + y + " out of bounds: width=" + width + ", height=" + height);
        return array[y*width + x];
    }

    /** @return a tile at coordinates, clamped. */
    public Tile getc(int x, int y){
        x = Mathf.clamp(x, 0, width - 1);
        y = Mathf.clamp(y, 0, height - 1);
        return array[y*width + x];
    }

    /** @return a tile at an iteration index [0, width * height] */
    public Tile geti(int idx){
        return array[idx];
    }

    /** @return a tile at an int position (not equivalent to geti) */
    public @Nullable Tile getp(int pos){
        return get(Point2.x(pos), Point2.y(pos));
    }

    public void eachTile(Cons<Tile> cons){
        for(Tile tile : array){
            cons.get(tile);
        }
    }

    @Override
    public Iterator<Tile> iterator(){
        //iterating through the entire map is expensive anyway, so a new allocation doesn't make much of a difference
        return new TileIterator();
    }

    private class TileIterator implements Iterator<Tile>{
        int index = 0;

        TileIterator(){
        }

        @Override
        public boolean hasNext(){
            return index < array.length;
        }

        @Override
        public Tile next(){
            return array[index++];
        }
    }
}
