/*
 * File      : tetris_modal.c
 * This file is part of RTGUI in RT-Thread RTOS
 * COPYRIGHT (C) 2010, RT-Thread Development Team
 *
 * The license and distribution terms for this file may be
 * found in the file LICENSE in this distribution or at
 * http://www.rt-thread.org/license/LICENSE
 *
 * Change Logs:
 * Date           Author       Notes
 * 2010-08-14     Yi.Qiu       first version
 */
 
#include <rtthread.h>
#include <stdlib.h> 
#include "tetris.h"

struct rt_tetris
{
	rt_uint32_t	width;			/* the width of the tetris */
	rt_uint32_t	height;			/* the height of the tetris */
	rt_uint16_t* 	panel;			/* the panel of the tetris */
	rt_uint32_t* 	brick;			/* the current brick of the tetris */
	rt_uint32_t* 	next_brick;		/* the next brick  of the tetris */
	rt_tetris_view_t* view;			/* the view on which the tetris show */
	rt_uint32_t 	level;			/* game level */
	rt_uint32_t 	lines;			/* released lines count */
	rt_uint32_t 	score;			/* total scores statistic */
	rt_bool_t 		status;			/* game status, pause or runing */
};

static const rt_uint32_t g_brick[][4] = 
{
	{23,7,8,22},
	{23,7,8,24},
	{24,7,8,25},
	{8,7,9,23},	
	{8,7,9,24},
	{8,7,9,25},
	{7,6,8,9},
};

static rt_err_t rt_tetris_append_brick(rt_tetris_t* thiz, rt_uint32_t brick[]);
static rt_err_t rt_tetris_delete_brick(rt_tetris_t* thiz, rt_uint32_t brick[]);
static rt_err_t rt_tetris_release_lines(rt_tetris_t* thiz, rt_uint32_t brick[]);
static rt_err_t rt_tetris_is_reach_top(rt_tetris_t* thiz, rt_uint32_t brick[]);
static rt_err_t rt_tetris_update_brick(rt_tetris_t* thiz);

/**
 * this function create a tetris instance
 *
 * @param width the width of tetris.
 * @param height the height of tetris.
 *
 * @return the tetris instance
 */
rt_tetris_t* rt_tetris_create(rt_uint32_t width, rt_uint32_t height)
{
	int index;

	rt_tetris_t* thiz = (rt_tetris_t*)rt_malloc(sizeof(rt_tetris_t));
	RT_ASSERT(thiz != RT_NULL);

	thiz->height 	= height;
	thiz->width		= width;
	thiz->panel 	= rt_malloc(thiz->height * sizeof(rt_uint32_t));
	rt_memset(thiz->panel, 0, thiz->height * sizeof(rt_uint32_t));	

	thiz->brick		= (rt_uint32_t*)rt_malloc(4 * sizeof(rt_uint32_t));
	index 			= (int)(7.0 * rand()/(RAND_MAX + 1.0)); 
	rt_memcpy(thiz->brick, g_brick[index], 4 * sizeof(rt_uint32_t));

	thiz->next_brick= (rt_uint32_t*)rt_malloc(4 * sizeof(rt_uint32_t));
	index 			= (int)(7.0 * rand()/(RAND_MAX + 1.0)); 
	rt_memcpy(thiz->next_brick, g_brick[index], 4 * sizeof(rt_uint32_t));

	thiz->view		= RT_NULL;
	thiz->level		= 0;
	thiz->lines 	= 0;
	thiz->score		= 0;
	thiz->status	= RT_FALSE;

	return thiz;
}

/**
 * this function destory a tetris instance
 *
 * @param thiz the tetris instance.
 *
 * @return RT_EOK
 */
rt_err_t rt_tetris_destory(rt_tetris_t* thiz)
{
	RT_ASSERT(thiz->panel && thiz->brick && thiz->next_brick);

	rt_free(thiz->panel);
	rt_free(thiz->brick);
	rt_free(thiz->next_brick);
	rt_free(thiz);

	return RT_EOK;
}

/**
 * this function start tetris game
 *
 * @param thiz the tetris instance.
 *
 * @return RT_EOK
 */
rt_err_t rt_tetris_start(rt_tetris_t* thiz)
{
	RT_ASSERT(thiz != RT_NULL);

	/* update next brick on view */
	thiz->view->update_next_brick(thiz->view, thiz);

	/* update level */
	thiz->view->update_level(thiz->view, thiz);

	/* update lines and score */
	thiz->view->update_score_and_lines(thiz->view, thiz);

	thiz->status = RT_TRUE;

	return RT_EOK;
}

/**
 * this function pause tetris game
 *
 * @param thiz the tetris instance.
 *
 * @return RT_EOK
 */
rt_err_t rt_tetris_pause(rt_tetris_t* thiz)
{
	RT_ASSERT(thiz != RT_NULL);

	thiz->status = RT_FALSE;

	return RT_EOK;
}

/**
 * this function get width of a tetris instance
 *
 * @param thiz the tetris instance.
 *
 * @return the width of the tetris instance
 */
rt_uint32_t rt_tetris_width(rt_tetris_t* thiz)
{
	RT_ASSERT(thiz != RT_NULL);

	return thiz->width;
}

/**
 * this function get next brick of a tetris instance
 *
 * @param thiz the tetris instance.
 *
 * @return the next brick of the tetris instance
 */
rt_uint32_t* rt_tetris_next_brick(rt_tetris_t* thiz)
{
	RT_ASSERT(thiz != RT_NULL);

	return thiz->next_brick;
}

/**
 * this function get level of the tetris instance
 *
 * @param thiz the tetris instance.
 *
 * @return the level of the tetris instance
 */
rt_uint32_t rt_tetris_level(rt_tetris_t* thiz)
{
	RT_ASSERT(thiz != RT_NULL);

	return thiz->level;
}

/**
 * this function get released lines of the tetris instance
 *
 * @param thiz the tetris instance.
 *
 * @return the released lines of the tetris instance
 */
rt_uint32_t rt_tetris_lines(rt_tetris_t* thiz)
{
	RT_ASSERT(thiz != RT_NULL);

	return thiz->lines;
}

/**
 * this function get score of the tetris instance
 *
 * @param thiz the tetris instance.
 *
 * @return the score of the tetris instance
 */
rt_uint32_t rt_tetris_score(rt_tetris_t* thiz)
{
	RT_ASSERT(thiz != RT_NULL);

	return thiz->score;
}

/**
 * this function get height of a tetris instance
 *
 * @param thiz the tetris instance.
 *
 * @return the height of the tetris instance
 */
rt_uint32_t rt_tetris_height(rt_tetris_t* thiz)
{
	RT_ASSERT(thiz != RT_NULL);	

	return thiz->height;
}

/**
 * this function get status of a tetris instance
 *
 * @param thiz the tetris instance.
 *
 * @return the status of the tetris instance
 */
rt_bool_t rt_tetris_status(rt_tetris_t* thiz)
{
	RT_ASSERT(thiz != RT_NULL);	

	return thiz->status;
}

/**
 * this function makes current brick move down
 *
 * @param thiz the tetris instance.
 *
 * @return RT_EOK on success, -RT_ERROR on fail
 */
rt_err_t rt_tetris_down(rt_tetris_t* thiz)
{
	int i;

	RT_ASSERT(thiz != RT_NULL);	

	if(thiz->status == RT_FALSE) return -RT_ERROR;

	/* delete the brick from tetris panel */
	rt_tetris_delete_brick(thiz, thiz->brick);

	for(i=0; i<4; i++)
	{
		/* check collision and bottom*/
		if((thiz->brick[i] >= thiz->width * (thiz->height - 1))
			|| rt_tetris_check_collision(thiz, thiz->brick[i] + thiz->width) == RT_EOK)
		{
			/* restore the deleted brick */
			rt_tetris_append_brick(thiz, thiz->brick);

			if(rt_tetris_is_reach_top(thiz, thiz->brick) == RT_EOK)
			{
				rt_memset(thiz->panel, 0xff, thiz->height * sizeof(rt_uint32_t));				

				/* update view */
				thiz->view->update(thiz->view, thiz);

				/* game over */
				return -RT_ETIMEOUT;
			}

			if(rt_tetris_release_lines(thiz, thiz->brick) == RT_EOK) 
			{
				/* update view */
				thiz->view->update(thiz->view, thiz);
			}

			rt_tetris_update_brick(thiz);
			return -RT_ERROR;			
		}
	}	

	for(i=0; i<4; i++)
	{	
		/* increase one line */
		thiz->brick[i] += thiz->width;
	}		

	/* append the brick to tetris panel */
	rt_tetris_append_brick(thiz, thiz->brick);

	/* update view */
	thiz->view->update(thiz->view, thiz);

	return RT_EOK;
}

/**
 * this function makes current brick move left
 *
 * @param thiz the tetris instance.
 *
 * @return RT_EOK on success, -RT_ERROR on fail
 */
rt_err_t rt_tetris_left(rt_tetris_t* thiz)
{
	int i;
	RT_ASSERT(thiz != RT_NULL);	

	if(thiz->status == RT_FALSE) return -RT_ERROR;

	/* delete the brick from tetris panel */
	rt_tetris_delete_brick(thiz, thiz->brick);

	for(i=0; i<4; i++)
	{
		/* check left board */
		if((thiz->brick[i] % thiz->width) == 0)
		{	
			/* restore the deleted brick */
			rt_tetris_append_brick(thiz, thiz->brick);
			return -RT_ERROR;
		}

		if(rt_tetris_check_collision(thiz, thiz->brick[i] - 1) == RT_EOK)
		{	
			/* restore the deleted brick */
			rt_tetris_append_brick(thiz, thiz->brick);
			return -RT_ERROR;
		}
	}	
	
	for(i=0; i<4; i++)
	{	
		/* move one step to left */
		thiz->brick[i] --;;
	}		

	/* append the brick to tetris panel */
	rt_tetris_append_brick(thiz, thiz->brick);

	/* update view */
	thiz->view->update(thiz->view, thiz);

	return RT_EOK;
}

/**
 * this function makes current brick move right
 *
 * @param thiz the tetris instance.
 *
 * @return RT_EOK on success, -RT_ERROR on fail
 */
rt_err_t rt_tetris_right(rt_tetris_t* thiz)
{
	int i;
	RT_ASSERT(thiz != RT_NULL);	

	if(thiz->status == RT_FALSE) return -RT_ERROR;

	/* delete the brick from tetris panel */
	rt_tetris_delete_brick(thiz, thiz->brick);

	for(i=0; i<4; i++)
	{
		/* check left board */
		if(((thiz->brick[i] + 1) % thiz->width) == 0)
		{	
			/* restore the deleted brick */
			rt_tetris_append_brick(thiz, thiz->brick);
			return -RT_ERROR;
		}

		/* check collision */
		if(rt_tetris_check_collision(thiz, thiz->brick[i] + 1) == RT_EOK)
		{	
			/* restore the deleted brick */
			rt_tetris_append_brick(thiz, thiz->brick);
			return -RT_ERROR;
		}
	}	
	
	for(i=0; i<4; i++)
	{	
		/* move one step to right */
		thiz->brick[i] ++;;
	}		

	/* append the brick to tetris panel */
	rt_tetris_append_brick(thiz, thiz->brick);

	/* update view */
	thiz->view->update(thiz->view, thiz);

	return RT_EOK;
}

/**
 * this function makes current brick drop quickly
 *
 * @param thiz the tetris instance.
 *
 * @return RT_EOK on success, -RT_ERROR on fail
 */
rt_err_t rt_tetris_drop(rt_tetris_t* thiz)
{
	rt_err_t ret;
	RT_ASSERT(thiz != RT_NULL);

	if(thiz->status == RT_FALSE) return -RT_ETIMEOUT;

	/* move down until blocked */
	while((ret = rt_tetris_down(thiz)) == RT_EOK);

	return ret;
}

/**
 * this function makes current brick do rotation
 *
 * @param thiz the tetris instance.
 *
 * @return RT_EOK on success, -RT_ERROR on fail
 */
rt_err_t rt_tetris_rotate(rt_tetris_t* thiz, rt_bool_t direction)
{
	int i;
	rt_uint32_t tmp[4];
	RT_ASSERT(thiz != RT_NULL);	

	if(thiz->status == RT_FALSE) return -RT_ERROR;

	rt_tetris_delete_brick(thiz, thiz->brick);

	tmp[0] = thiz->brick[0];
	for(i=1; i<4; i++)
	{
		int diff = thiz->brick[0] - thiz->brick[i];
		if(diff == 1)
		{
			tmp[i] = thiz->brick[0] - thiz->width;
		}
		else if(diff == -1)
		{
			tmp[i] = thiz->brick[0] + thiz->width;
		}
		else if(diff == 2)
		{
			tmp[i] = thiz->brick[0] - 2 * thiz->width;
		}
		else if(diff == -2)
		{
			tmp[i] = thiz->brick[0] + 2 * thiz->width;
		}
		else if(diff == thiz->width - 1)
		{
			tmp[i] = thiz->brick[0] + thiz->width + 1;
		}
		else if(diff == 1 - thiz->width)
		{
			tmp[i] = thiz->brick[0] - thiz->width - 1;
		}
		else if(diff == thiz->width)
		{
			if((thiz->brick[0] + 1) % thiz->width == 0) 
			{
				/* restore the deleted brick */
				rt_tetris_append_brick(thiz, thiz->brick);				
				return -RT_ERROR;
			}
			else tmp[i] = thiz->brick[0] + 1;
		}
		else if(diff == -1 * (thiz->width))
		{
			if(thiz->brick[0] % thiz->width == 0)
			{	
				/* restore the deleted brick */
				rt_tetris_append_brick(thiz, thiz->brick);			
				return -RT_ERROR;
			}
			else tmp[i] = thiz->brick[0] - 1;
		}
		else if(diff == thiz->width + 1)
		{
			tmp[i] = thiz->brick[0] - thiz->width + 1;
		}
		else if(diff == -1 - thiz->width)
		{
			tmp[i] = thiz->brick[0] + thiz->width - 1;
		}
		else if(diff == 2 * thiz->width)
		{
			if((thiz->brick[0] % thiz->width) >= (thiz->width - 2)) 
			{
				/* restore the deleted brick */
				rt_tetris_append_brick(thiz, thiz->brick);			
				return -RT_ERROR;
			}
			else tmp[i] = thiz->brick[0] + 2;
		}
		else if(diff == -2 * thiz->width)
		{
			if((thiz->brick[0] % thiz->width) < 2) 
			{
				/* restore the deleted brick */
				rt_tetris_append_brick(thiz, thiz->brick);			
				return -RT_ERROR;
			}
			else tmp[i] = thiz->brick[0] - 2;
		}
		
		if(tmp[i] > (thiz->height) * thiz->width) 
		{
			/* restore the deleted brick */
			rt_tetris_append_brick(thiz, thiz->brick);
			return -RT_ERROR;		
		}

		if(rt_tetris_check_collision(thiz, tmp[i]) == RT_EOK)
		{
			/* restore the deleted brick */
			rt_tetris_append_brick(thiz, thiz->brick);
			return -RT_ERROR;		
		}
	}

	/* do roration */
	for(i=0; i<4; i++)
	{
		thiz->brick[i] = tmp[i];
	}

	/* append the brick to tetris panel */
	rt_tetris_append_brick(thiz, thiz->brick);

	/* update view */
	thiz->view->update(thiz->view, thiz);

	return RT_EOK;
}

/**
 * this function add a view to the tetris
 *
 * @param thiz the tetris instance.
 * @param view the view instance.
 *
 * @return RT_EOK on success, -RT_ERROR on fail
 */
rt_err_t rt_tetris_add_view(rt_tetris_t* thiz, rt_tetris_view_t* view)
{
	RT_ASSERT(thiz != RT_NULL);

	/* Only suppurt single view now */
	thiz->view = view;

	return RT_EOK;
}

/**
 * this function delete a view from the tetris
 *
 * @param thiz the tetris instance.
 * @param view the view instance.
 *
 * @return RT_EOK on success, -RT_ERROR on fail
 */

rt_err_t rt_tetris_delete_view(rt_tetris_t* thiz, rt_tetris_view_t* view)
{
	RT_ASSERT(thiz != RT_NULL);

	thiz->view = RT_NULL;

	return RT_EOK;
}

/**
 * this function used to check collision
 *
 * @param thiz the tetris instance.
 * @param block the block to be checked.
 *
 * @return RT_EOK on collision, -RT_ERROR on not collision
 */
rt_err_t rt_tetris_check_collision(rt_tetris_t* thiz, rt_uint32_t block)
{
	RT_ASSERT(thiz != RT_NULL);
	RT_ASSERT(block < thiz->height * thiz->width);

	if((thiz->panel[block/thiz->width] & (1 << (block % thiz->width)))
		== (1 << (block % thiz->width)))
	{
		return RT_EOK;
	}
	else
	{
		return -RT_ERROR;
	}
}

static rt_err_t rt_tetris_update_brick(rt_tetris_t* thiz)
{
	int index;

	RT_ASSERT(thiz != RT_NULL);

	index = (int)(7.0 * rand()/(RAND_MAX + 1.0)); 

	rt_memcpy(thiz->brick, thiz->next_brick, 4 * sizeof(rt_uint32_t));
	rt_memcpy(thiz->next_brick, g_brick[index], 4 * sizeof(rt_uint32_t));

	/* update next brick on view */
	thiz->view->update_next_brick(thiz->view, thiz);
		
	return RT_EOK;
}

static rt_err_t rt_tetris_append_brick(rt_tetris_t* thiz, rt_uint32_t brick[])
{
	int i;

	RT_ASSERT(thiz != RT_NULL);
	RT_ASSERT(brick != RT_NULL);

	for(i=0; i<4; i++)
	{
		int y = brick[i]/thiz->width;
		int x = brick[i]%thiz->width;

		thiz->panel[y] |= (1<<x);
	}

	return RT_EOK;
}

static rt_err_t rt_tetris_delete_brick(rt_tetris_t* thiz, rt_uint32_t brick[])
{
	int i;

	RT_ASSERT(thiz != RT_NULL);
	RT_ASSERT(brick != RT_NULL);

	for(i=0; i<4; i++)
	{
		int y = brick[i]/thiz->width;
		int x = brick[i]%thiz->width;

		thiz->panel[y] &= ~(1<<x);
	}

	return RT_EOK;
}

static rt_err_t rt_tetris_is_reach_top(rt_tetris_t* thiz, rt_uint32_t brick[])
{
	int i;

	RT_ASSERT(thiz != RT_NULL);
	RT_ASSERT(brick != RT_NULL);

	for(i=0; i<4; i++)	
	{
		if(brick[i] / thiz->width == 0)
			return RT_EOK;
	}

	return -RT_ERROR;
}

static rt_err_t rt_tetris_release_lines(rt_tetris_t* thiz, rt_uint32_t brick[])
{
	int i, j, check_line = 0;
	rt_bool_t line_released = -RT_ERROR;

	RT_ASSERT(thiz != RT_NULL);
	RT_ASSERT(brick != RT_NULL);

	for(i=0; i<4; i++)
	{
		/* choose a line */
		check_line = brick[i]/thiz->width;	
		if((thiz->panel[check_line]) == ((1 << thiz->width) - 1))
		{
			for(j=check_line; j>0; j--)
			{
				thiz->panel[j] = thiz->panel[j-1];
			}
			
			/* clear the first line */
			thiz->panel[0] = 0;
			
			for(j=i+1; j<4; j++)
			{
				if(brick[j] < brick[i])
				{
					brick[j] += thiz->width;
				}
			}
			
			thiz->lines++;
			thiz->score += 100;
			line_released = RT_EOK;				
		}
	}

	if(line_released == RT_EOK)
	{
		/* update view */
		thiz->view->update_score_and_lines(thiz->view, thiz);
		return RT_EOK;
	}
	else
	{
		return -RT_ERROR;
	}
}