import java.util.*;
import java.awt.*;
import java.net.*;

class Game {
	static final int RESTING 	= 0;
	static final int HOLDING 	= 1;
	static final int SWAPPING 	= 2;
	static final int COLLAPSING	= 3;
	
	final Color LIGHT_COLOR	= new Color(0x000033);
	final Color DARK_COLOR 	= new Color(0x001144);
	final Color HIGHLIGHT_COLOR = new Color(0x003366);
	final double EASE_SPEED = 2.2;
	final double GRAVITY = 3;
	final int COLORS = 7;
	final long HM = 7;
	final long VM = 65793;
	final int GEM_SIZE = 60;
	final Point OFFSET;
	final Image[] GEM_IMAGES = new Image[COLORS];
	final String[] GEM_NAMES = new String[] { "gem_white", "gem_yellow", "gem_orange",
											  "gem_red", "gem_purple", "gem_blue", "gem_green" };
	final Move[] MOVES = new Move[] {
		new Move(131330, 2, 3, new Point(0,1)),
		new Move(1282, 3, 2, new Point(1,0)),
		new Move(66049, 2, 3, new Point(1,1)),
		new Move(517, 3, 2, new Point(1,1)), 
		new Move(131329, 2, 3, new Point(1,2)),
		new Move(772, 3, 2, new Point(2,0)),
		new Move(66050, 2, 3, new Point(0,2)),
		new Move(1027, 3, 2, new Point(2,1)),
		new Move(65794, 2, 3, new Point(1,0)),
		new Move(1537, 3, 2, new Point(0,0)),
		new Move(131585, 2, 3, new Point(0,0)),
		new Move(262, 3, 2, new Point(0,1)),
		new Move(16777473, 1, 4, new Point(0,3)),
		new Move(13, 4, 1, new Point(0,0)),
		new Move(16842753, 1, 4, new Point(0,0)),
		new Move(11, 4, 1, new Point(3,0))
	};
	
	Gem[] gems;	
	long[] board;
	int heldGem, swappingGem;
	int state;
	int score;
	
	Game(int x, int y) {
		OFFSET = new Point(x, y);
		for (int i = 0; i < COLORS; i++) {
			URL imgURL = GemSwapper.class.getResource("/resources/images/" + GEM_NAMES[i] + ".png");
			GEM_IMAGES[i] = Toolkit.getDefaultToolkit().getImage(imgURL).getScaledInstance(50, 56, Image.SCALE_SMOOTH);
		}
		randomBoard();
		state = RESTING;
		score = 0;
	}
	
	synchronized void randomBoard() {
		int c;
		gems = new Gem[64];
		board = new long[COLORS];
		Arrays.fill(board, 0);
		for (int i = 0; i < 64; i++) {
			c = (int)(Math.random()*COLORS);
			do {
				board[c] &= ~((long)1 << i);
				c = (c+1)%COLORS;
				board[c] |= (long)1 << i;
			} while (matches() != 0);
			gems[i] = new Gem(c, new Point((i%8)*GEM_SIZE, (i/8)*GEM_SIZE - 8*GEM_SIZE));
			gems[i].newPosition = new Point((i%8)*GEM_SIZE, (i/8)*GEM_SIZE);
		}
	}
	
	long matches() {
		long ret = 0;
		long mask;
		for (int i = 0; i < 6; i++) {
			for (int j = 0; j < 8; j++) {
				for (long b : board) {
					mask = HM << (8*j + i);
					if ((b & mask) == mask) ret |= mask;
					mask = VM << (8*i + j);
					if ((b & mask) == mask) ret |= mask;
				}
			}
		}
		return ret;
	}
	
	long swapBits(long b, int p1, int p2) {
		long m1 = (long)1 << p1;
		long m2 = (long)1 << p2;
		long b1 = (b&m1) == 0 ? 0 : 1;
		long b2 = (b&m2) == 0 ? 0 : 1;
		if (b1 + b2 == 1) {
			b &= (~m1)&(~m2);
			b |= (b2<<p1)|(b1<<p2);
		}
		return b;
	}
	
	boolean inBoard(int x, int y) {
		return x/GEM_SIZE >= 0 && x/GEM_SIZE < 8 && y/GEM_SIZE >= 0 && y/GEM_SIZE < 8;
	}
	
	synchronized void holdGem(int x, int y) {
		if (state == RESTING && inBoard(x, y)) {
			int pos = (y/GEM_SIZE)*8+(x/GEM_SIZE);
			if (pos >= 0 && pos < 64) {
				gems[pos].selected = true;
				heldGem = pos;
				state = HOLDING;
			}
		}
	}
	
	synchronized void slideGem(int x, int y) {
		if (state == HOLDING && inBoard(x, y)) {
			int pos = (y/GEM_SIZE)*8+(x/GEM_SIZE);
			if (pos >= 0 && pos < 64 && pos != heldGem &&
				(pos-heldGem == 1 || pos-heldGem == -1 || pos-heldGem == 8 || pos-heldGem == -8))
					dropGem(x, y);
		}
	}
	
	synchronized void dropGem(int x, int y) {
		if (state == HOLDING && inBoard(x, y)) {
			int pos = (y/GEM_SIZE)*8+(x/GEM_SIZE);
			if (pos >= 0 && pos < 64 && pos != heldGem &&
				(pos-heldGem == 1 || pos-heldGem == -1 || pos-heldGem == 8 || pos-heldGem == -8)) {
					state = SWAPPING;
					swappingGem = pos;
					swapGems(heldGem, swappingGem);
			} else state = RESTING;
		}
	}
	
	synchronized void swapGems(int gem1, int gem2) {
		board[gems[gem1].color] = swapBits(board[gems[gem1].color], gem1, gem2);
		board[gems[gem2].color] = swapBits(board[gems[gem2].color], gem1, gem2);
		Gem temp = gems[gem1];
		gems[gem1] = gems[gem2];
		gems[gem2] = temp;
		gems[gem1].newPosition = new Point(gems[gem2].position);
		gems[gem2].newPosition = new Point(gems[gem1].position);
	}
	
	synchronized void removeGems() {
		long matches = matches();
		for (int i = 0; i < 64; i++) {
			if ((matches & ((long)1 << i)) != 0) {
				score++;
				for (int j = i; j >= 8; j-= 8) {
					gems[j].color = gems[j-8].color;
					gems[j].position = new Point(gems[j-8].position.x, gems[j-8].position.y);
					for (int k = 0; k < COLORS; k++) board[k] = swapBits(board[k], j, j-8);
				}
				gems[i%8] = new Gem((int)(Math.random()*COLORS), new Point((i%8)*GEM_SIZE, 0));
				if (i >= 8) {
					gems[i%8].position = new Point(gems[i%8+8].position.x, gems[i%8+8].position.y-GEM_SIZE);
					gems[i%8].newPosition = new Point(gems[i%8+8].newPosition.x, gems[i%8+8].newPosition.y-GEM_SIZE);
				} else {
					gems[i%8].position = new Point(gems[i%8+8].position.x, -GEM_SIZE);
					gems[i%8].newPosition = new Point(gems[i%8+8].newPosition.x, 0);
				}
				for (int j = 0; j < COLORS; j++) board[j] &= ~((long)1 << (i%8));
				board[gems[i%8].color] |= (long)1 << (i%8);
				matches &= ~((long)1 << i);
			}
		}
	}
	
	void drawHighlight(int gem, Graphics g) {
		g.setColor(HIGHLIGHT_COLOR);
		g.drawRect(gems[gem].position.x + OFFSET.x - 5, gems[gem].position.y + OFFSET.y - 2, GEM_SIZE, GEM_SIZE);
		g.drawRect(gems[gem].position.x + OFFSET.x - 4, gems[gem].position.y + OFFSET.y - 1, GEM_SIZE - 2, GEM_SIZE - 2);
		g.drawRect(gems[gem].position.x + OFFSET.x - 3, gems[gem].position.y + OFFSET.y, GEM_SIZE - 4, GEM_SIZE - 4);
	}
	
	synchronized void drawBoard(Graphics g) {
		for (int i = 0; i < 8; i++)
			for (int j = 0; j < 8; j++) {
				g.setColor((i + j) % 2 == 0 ? DARK_COLOR : LIGHT_COLOR);
				g.fillRect(j*GEM_SIZE + OFFSET.x - 5, i*GEM_SIZE + OFFSET.y - 2, GEM_SIZE, GEM_SIZE);
			}
		for (Gem gem : gems) {
			if (state == SWAPPING || (state == RESTING && isAnimating())) {
				if (!gem.position.equals(gem.newPosition)) {	
					double dx = (gem.newPosition.x - gem.position.x) / (double)EASE_SPEED;
					double dy = (gem.newPosition.y - gem.position.y) / (double)EASE_SPEED;
					if (dx != 0 && Math.abs(dx) < 1) dx = dx > 0 ? 1 : -1;
					if (dy != 0 && Math.abs(dy) < 1) dy = dy > 0 ? 1 : -1;
					gem.position.x += dx;
					gem.position.y += dy;
				}
			} else if (state == COLLAPSING) {
				if (!gem.position.equals(gem.newPosition)) {
					gem.velocity += GRAVITY;
					gem.position.y = (int)Math.min(gem.newPosition.y, gem.position.y + gem.velocity);
				}
			}
			g.drawImage(GEM_IMAGES[gem.color], gem.position.x + OFFSET.x, gem.position.y + OFFSET.y, null);
		}
		if (state == HOLDING) drawHighlight(heldGem, g);
		if (state != RESTING && !isAnimating()) {
			if (state == SWAPPING) {
				state = RESTING;
				if (matches() == 0) swapGems(heldGem, swappingGem);
				else state = COLLAPSING;
			} else if (state == COLLAPSING) {
				if (matches() == 0) state = RESTING;
				for (Gem gem : gems) gem.velocity = 0;
			}
		}
	}
	
	int getMove() {
		for (Move m : MOVES)
			for (long b : board)
				for (int x = 0; x < 9-m.width; x++) 
					for (int y = 0; y < 9-m.height; y++)
						if (((m.mask << (y*8+x)) & b) == (m.mask << (y*8+x)))
							return (y + m.pos.y)*8 + (x + m.pos.x);
		return -1;
	}
	
	synchronized boolean isAnimating() {
		for (Gem gem : gems)
			if (!gem.position.equals(gem.newPosition))
				return true;
		return false;
	}
}
