
#include "spec.h"

#define log(txt,line) Prnt((txt), W, line, White);

#define maxVoxel 1000
#define W 81
#define H 25
#define Col 80
#define Row 25

#define AlreadyMoved 0x80
#define IsAlreadyMoved(v) ((v) & AlreadyMoved)
#define WithoutFlag (AlreadyMoved ^ 0xFF)
typedef enum Voxel {
	Empty = 0,
	Sand = 1,
	Water = 2,
	Rock = 3,
	Bone = 4,
	Wheel = 5,
	BedRock
}Voxel;

typedef enum Actor {
        Player,
        N
}Actor;

typedef struct Entity{
    Let x;
    Let y;
    Voxel voxel;
}Entity;
static const Entity Nothing = (Entity){W,H,Empty};

typedef struct Grid{
    Voxel matrix[W][H];
    Entity entity[N];
}Grid;

static Grid grid = {{Empty},{(Entity){W,H,Empty}}};

void ClearFlag(){
    for (int x=0;x<W;x++)
    for (int y=0;y<H;y++)
    grid.matrix[x][y] &= WithoutFlag;
    for (int i=0;i<N;i++)
    grid.entity[i].voxel &= WithoutFlag;
}

void Set(Actor actor,Let x, Let y, Voxel voxel){
    if (actor < 0) return;
    if (actor >= N) return;
    grid.entity[actor] = (Entity){x,y,voxel};
}

Entity Get(Actor actor){
    if (actor >= 0)
    if (actor < N)
    return grid.entity[actor];
    return Nothing;
}


void Put(Let x,Let y,Voxel voxel){
    if (x >= W) return;
    if (y >= H) return;

    for (int i=0;i<N;i++)
    if (grid.entity[i].voxel != Empty)
    if (grid.entity[i].x == x)
    if (grid.entity[i].y == y)
    return;  

    grid.matrix[x][y] = voxel;
}

Voxel At(Let x, Let y){
    if (x >= W) return BedRock;
    if (y >= H) return BedRock;

    for (int i=0;i<N;i++)
    if (grid.entity[i].voxel != Empty)
    if (grid.entity[i].x == x)
    if (grid.entity[i].y == y)
    return grid.entity[i].voxel & WithoutFlag;

    return grid.matrix[x][y] & WithoutFlag;
}

void Swap(Let x1, Let y1, Let x2, Let y2){
    if (x1 >= W) return;
    if (y1 >= H) return;
    if (x2 >= W) return;
    if (y2 >= H) return;

    if (IsAlreadyMoved(grid.matrix[x1][y1])) return;
    if (IsAlreadyMoved(grid.matrix[x2][y2])) return;

    Voxel tmp = grid.matrix[x1][y1];
    grid.matrix[x1][y1] = grid.matrix[x2][y2];
    grid.matrix[x2][y2] = tmp;

    if (grid.matrix[x2][y2] != Empty)
    grid.matrix[x2][y2] |= AlreadyMoved;

    for (int i=0;i<N;i++)
    if (grid.entity[i].x == x1 && grid.entity[i].y == y1)
    grid.entity[i] = (Entity){x2, y2, grid.entity[i].voxel |= AlreadyMoved};
    else if (grid.entity[i].x == x2 && grid.entity[i].y == y2)
    grid.entity[i] = (Entity){x1, y1, grid.entity[i].voxel |= AlreadyMoved};
}

Let WrapLeft(Let x){
	if (W == 0xFF) return x;
	if (x < W) return x;
	return W - 1 - (0xFF - x);
}

Let WrapRight(Let x){
	if (W == 0xFF) return x;
	if (x < W) return x;
	return x - W;
}

Let WrapUp(Let y){
	if (H == 0xFF) return y;
	if (y < H) return y;
	return H - (0xFF - y) - 1;
}

Let WrapDown(Let y){
	if (H == 0xFF) return y;
	if (y < H) return y;
	return y - H;
}

Let IncY(Let y,Let n){
	for (int i=0;i<n;i++)
	y = WrapDown(y+1);
	return y;
}

Let DecY(Let y,Let n){
	for (int i=0;i<n;i++)
	y = WrapUp(y-1);
	return y;
}

Let IncX(Let x,Let n){
	for (int i=0;i<n;i++)
	x = WrapRight(x+1);
	return x;
}

Let DecX(Let x,Let n){
	for (int i=0;i<n;i++)
	x = WrapLeft(x-1);
	return x;
}

#define SwapLeft(x,y) Swap((x),(y), DecX(x,1) ,(y))
#define OnLeft(x,y) At( DecX(x,1) ,(y))

#define SwapRight(x,y) Swap((x),(y), IncX(x,1) ,(y))
#define OnRight(x,y) At( IncX(x,1) ,(y))

#define SwapDown(x,y) Swap((x),(y),(x), IncY(y,1) )
#define Under(x,y) At((x), IncY(y,1) )

#define SwapUp(x,y) Swap((x),(y),(x), DecY(y,1) )
#define Above(x,y) At((x), DecY(y,1) )

#define DownRight(x,y) At( IncX((x),1), IncY((y),1) )
#define SwapDownRight(x,y) Swap((x),(y), IncX((x),1) , IncY((y),1) )

#define DownLeft(x,y) At( DecX((x),1) , IncY((y),1) )
#define SwapDownLeft(x,y) Swap((x),(y), DecX((x),1) , IncY((y),1) )

#define UpRight(x,y) At( IncX((x),1) , DecY((y),1) )
#define SwapUpRight(x,y) Swap((x),(y), IncX((x),1) , DecY((y),1) )

#define UpLeft(x,y) At( DecX((x),1) , DecY((y),1) )
#define SwapUpLeft(x,y) Swap((x),(y), DecX((x),1) , DecY((y),1) )

void Fall(){
	//maybe plot the entity first with a bitmask
	//only check for entity if marked
	//scan entity first and process them
	//after scan every cell whom not marked entity
	//improve performance by many fold
	for (int x = 0; x < W; x += 1)
	for (int y = 0; y < H; y += 1)
	switch(At(x,y)){
		case Sand:
			if (Under(x,y) == Empty)
				SwapDown(x,y);
			else if(Under(x,y) == Water)
				SwapDown(x,y);
			else if (DownRight(x,y) == Empty)
				SwapDownRight(x,y);
			else if (DownRight(x,y) == Water)
				SwapDownRight(x,y);
			else if (DownLeft(x,y) == Empty)
				SwapDownLeft(x,y);
			else if (DownLeft(x,y) == Water)
				SwapDownLeft(x,y);
			break;
		case Water:
			if (Under(x,y) == Empty)
				SwapDown(x,y);
			else if (Under(x,y) == Bone)
				SwapDown(x,y);
			else if (OnLeft(x,y) == Empty)
				SwapLeft(x,y);
			else if (OnRight(x,y) == Empty)
				SwapRight(x,y);
			break;
		case Bone:
        	        if (Under(x,y) == Empty)
				SwapDown(x,y);
			break;
	}
	// moved once bit mask
	ClearFlag();
}

void Draw(Let scroll, Let vscroll){
	for (int x=0;x<Col;x++)
	for (int y=0;y<Row;y++){
		Let xs = IncX(x,scroll);
		Let ys = IncY(y,vscroll);
		Voxel current = At(xs,ys);
		switch(current){
			case Empty:
				Plt(x,y,Black);
				break;
			case BedRock:
				Plt(x,y,Whiteless);
				break;
			case Sand:	
				Plt(x,y,Green);
				break;
			case Water:
				Plt(x,y,Blue);
				break;
			case Rock:
				Plt(x,y,Yellow);
				break;
			case Bone:
				Prnt("!",x,y,White);
				break;
		}
	}
}


void VoxelGenerate(){
	Dice();
	Let x = (Dice() * Dice() * Dice() + Dice()) % W;
	Voxel spawn = Rock;
	Let dice = Dice();
	switch (dice){
		case 1:
		case 2:
		case 3:
		case 4:
		spawn = Sand;
		break;
		case 5:
		case 6:
		spawn = Water;
		break;
	}
	Put(x,0,spawn);
}


static int scroll = 0;
void LoopIt(Co c){
	Let x = Get(Player).x;
	Let y = Get(Player).y;
	
	if (Above(x,y) == Empty && Btn(Up))
		SwapUp(x,y);
	else if (Under(x,y) != Empty && Btn(Dn))
		Put(x,y+1,Empty);
	else if (OnLeft(x,y) == Empty && Btn(Lt))
		SwapLeft(x,y);
	else if (UpLeft(x,y) == Empty && Btn(Lt))
		SwapUpLeft(x,y);
	else if (OnRight(x,y) == Empty && Btn(Rt))
		SwapRight(x,y);
	else if (UpRight(x,y) == Empty && Btn(Rt))
		SwapUpRight(x,y);

	Fall();

	Let xs = DecX(x,20);
	Let ys = 0;

	Draw(xs,ys);
	PrntNum(x, 0, 1, Green);
	Prnt("x pos", 4,1,Green);


	QToExit(c,0);
}

void GenerateIt(Co c){
	for(int i = 0; i<W;i++)
	Put(i,H-1,Rock);

	for (int i=0;i<maxVoxel;i++){
		VoxelGenerate();
		Fall();
		//Draw();
		refresh();
	}

    Set(Player,10,10,Bone);

	c->routine = LoopIt;
}

void Artillery(Co c){
	for (int x=0;x<Col;x++)
	for (int y=0;y<Row;y++)
	Plt(x,y,Black);

	Prnt("0-1", 0,1,Cyan);
	PrntNum(0-1,4,1,Cyan);
	Prnt("W", 8,1,Cyan);
	PrntNum(W,12,1,Cyan);
	Prnt("H", 16,1,Cyan);
	PrntNum(H,20,1,Cyan);

	Prnt("WrapLeft(255)",0,3,Blue);
	PrntNum(WrapLeft(255),15,3,Blue);
	Prnt("WrapLeft(0-1)",0,4,Red);
	PrntNum(WrapLeft(0-1),15,4,Red);

	Prnt("WrapUp(255)",0,6,Blue);
	PrntNum(WrapUp(255),15,6,Blue);
	Prnt("WrapUp(0-1)",0,7,Red);
	PrntNum(WrapUp(0-1),15,7,Red);
	
	Prnt("WrapRight(W)",0,9,Blue);
	PrntNum(WrapRight(W),15,9,Blue);

	Prnt("WrapDown(H)",0,11,Blue);
	PrntNum(WrapDown(H),15,11,Blue);

	Prnt("DecX(0)",20,3,Blue);
	PrntNum(DecX(0,1),30,3,Blue);
	Prnt("DecX(1)",20,4,Blue);
	PrntNum(DecX(1,1),30,4,Blue);

	Prnt("IncX(0)",40,3,Blue);
	PrntNum(IncX(0,1),50,3,Blue);
	Prnt("IncX(W)",40,4,Blue);
	PrntNum(IncX(W,1),50,4,Blue);

	AnyKey(c,GenerateIt);
}
