/*
 * Copyright (c) 2019-2020, yzrh <yzrh@noema.org>
 *
 * SPDX-License-Identifier: Apache-2.0
 */

#include <stdbool.h>

#include "extern.h"
#include "screen.h"

static bool
_collision(int *fruit, Snake_Pos **snake)
{
	Snake_Pos *ptr = *snake;
	while (ptr != NULL) {
		if (collision_block(fruit, (int[]) {ptr->x, ptr->y}))
			return 1;
		ptr = ptr->next;
	}
	return 0;
}

static bool
_bound(Snake_Pos **snake)
{
	if ((*snake)->x + SCREEN_UNIT / 2 > SCREEN_HEIGHT)
		return 1;
	else if ((*snake)->y + SCREEN_UNIT / 2 > SCREEN_HEIGHT)
		return 1;
	else if ((*snake)->x - SCREEN_UNIT / 2 < -SCREEN_UNIT)
		return 1;
	else if ((*snake)->y - SCREEN_UNIT / 2 < -SCREEN_UNIT)
		return 1;
	return 0;
}

static int
_overlap(Snake_Pos **snake0, Snake_Pos **snake1)
{
	Snake_Pos *ptr0 = *snake0;
	Snake_Pos *ptr1;

	int is_overlap[2] = {0, 0};

	while (ptr0->next->next != NULL) {
		if (collision_block(
			(int[]) {(*snake0)->x, (*snake0)->y},
			(int[]) {ptr0->next->x, ptr0->next->y})) {
			is_overlap[0] = 1;
			break;
		}
		ptr0 = ptr0->next;
	}

	if (snake1 != NULL) {
		ptr0 = *snake0;
		ptr1 = *snake1;
		while (ptr0->next->next != NULL &&
			ptr1->next->next != NULL &&
			(is_overlap[0] != 1 || is_overlap[1] != 2)) {
			if (collision_block(
				(int[]) {(*snake0)->x, (*snake0)->y},
				(int[]) {ptr1->next->x, ptr1->next->y}))
				is_overlap[0] = 1;
			if (collision_block(
				(int[]) {(*snake1)->x, (*snake1)->y},
				(int[]) {ptr0->next->x, ptr0->next->y}))
				is_overlap[1] = 2;
			if (collision_block(
				(int[]) {(*snake1)->x, (*snake1)->y},
				(int[]) {ptr1->next->x, ptr1->next->y}))
				is_overlap[1] = 2;
			ptr0 = ptr0->next;
			ptr1 = ptr1->next;
		}
	}

	return is_overlap[0] + is_overlap[1];
}

void
game_snake_init(Snake_Pos **snake, int dx)
{
	Snake_Pos *ptr;
	while ((ptr = *snake) != NULL) {
		*snake = (*snake)->next;
		free(ptr);
	}

	*snake = malloc(sizeof(Snake_Pos));
	(*snake)->next = malloc(sizeof(Snake_Pos));

	(*snake)->x = (*snake)->next->x = SCREEN_HEIGHT / 2 + dx;
	(*snake)->y = SCREEN_HEIGHT / 2;
	(*snake)->next->y = SCREEN_HEIGHT / 2 + SCREEN_UNIT;
	(*snake)->next->next = NULL;
}

void
game_snake_move(Snake_Pos **snake0, Snake_Pos **snake1,
	Snake_Input *head, bool law, int *legal)
{
	bool bound_snake0;
	bool bound_snake1;
	int overlap;

	Snake_Pos *ptr0;
	Snake_Pos *ptr1;

	ptr0 = malloc(sizeof(Snake_Pos));
	ptr0->x = (*snake0)->x;
	ptr0->y = (*snake0)->y;
	ptr0->next = *snake0;

	switch (head[0]) {
		case SNAKE_LEFT:
			ptr0->x -= SCREEN_UNIT;
			break;
		case SNAKE_UP:
			ptr0->y -= SCREEN_UNIT;
			break;
		case SNAKE_DOWN:
			ptr0->y += SCREEN_UNIT;
			break;
		case SNAKE_RIGHT:
			ptr0->x += SCREEN_UNIT;
			break;
		default:
			break;
	}

	bound_snake0 = _bound(&ptr0);

	if (snake1 == NULL) {
		overlap = _overlap(&ptr0, NULL);

		if (bound_snake0 || overlap == 1)
			*legal = 1;
		else
			*legal = 0;
	} else {
		ptr1 = malloc(sizeof(Snake_Pos));
		ptr1->x = (*snake1)->x;
		ptr1->y = (*snake1)->y;
		ptr1->next = *snake1;

		switch (head[1]) {
			case SNAKE_LEFT:
				ptr1->x -= SCREEN_UNIT;
				break;
			case SNAKE_UP:
				ptr1->y -= SCREEN_UNIT;
				break;
			case SNAKE_DOWN:
				ptr1->y += SCREEN_UNIT;
				break;
			case SNAKE_RIGHT:
				ptr1->x += SCREEN_UNIT;
				break;
			default:
				break;
		}

		bound_snake1 = _bound(&ptr1);
		overlap = _overlap(&ptr0, &ptr1);

		if ((bound_snake0 && bound_snake1) || overlap == 3)
			*legal = 3;
		else if (bound_snake1 || overlap == 2)
			*legal = 2;
		else if (bound_snake0 || overlap == 1)
			*legal = 1;
		else
			*legal = 0;
	}

	if (*legal == 0 || !law) {
		*snake0 = ptr0;

		while (ptr0->next->next != NULL)
			ptr0 = ptr0->next;
		free(ptr0->next);
		ptr0->next = NULL;

		if (snake1 != NULL) {
			*snake1 = ptr1;

			while (ptr1->next->next != NULL)
				ptr1 = ptr1->next;
			free(ptr1->next);
			ptr1->next = NULL;
		}
	} else {
		free(ptr0);

		if (snake1 != NULL)
			free(ptr1);
	}
}

void
game_snake_grow(Snake_Pos **snake)
{
	Snake_Pos *ptr = *snake;
	while (ptr->next != NULL)
		ptr = ptr->next;
	ptr->next = malloc(sizeof(Snake_Pos));
	ptr->next->x = ptr->x;
	ptr->next->y = ptr->y;
	ptr->next->next = NULL;
}

void
game_fruit(int *fruit, Snake_Pos **snake)
{
	do {
		fruit[0] = rand() % SCREEN_SIDE * SCREEN_UNIT;
		fruit[1] = rand() % SCREEN_SIDE * SCREEN_UNIT;
	} while (_collision(fruit, snake));
}

void
game_snake_fall(Snake_Pos **snake, bool norm, int speed)
{
	Snake_Pos *ptr = *snake;
	while(ptr != NULL) {
		ptr->y += norm ? speed : -speed;
		ptr = ptr->next;
	}
}

void
game_pipe_init(Snake_Pos **pipe)
{
	Snake_Pos *ptr;
	while ((ptr = *pipe) != NULL) {
		*pipe = (*pipe)->next;
		free(ptr);
	}

	*pipe = malloc(sizeof(Snake_Pos));
	(*pipe)->x = SCREEN_HEIGHT;
	(*pipe)->y = (rand() % (SCREEN_SIDE - PIPE_GAP) + PIPE_GAP) *
		SCREEN_UNIT;
	(*pipe)->next = NULL;
}

void
game_pipe_move(Snake_Pos **pipe)
{
	Snake_Pos *ptr = *pipe;
	while(ptr != NULL) {
		ptr->x -= 1;
		ptr = ptr->next;
	}
}

void
game_pipe_add(Snake_Pos **pipe)
{
	Snake_Pos *ptr = *pipe;
	while(ptr->next != NULL)
		ptr = ptr->next;
	ptr->next = malloc(sizeof(Snake_Pos));
	ptr->next->x = SCREEN_HEIGHT;
	ptr->next->y = (rand() % (SCREEN_SIDE - PIPE_GAP) + PIPE_GAP) *
		SCREEN_UNIT;
	ptr->next->next = NULL;
}

void
game_pipe_del(Snake_Pos **pipe)
{
	Snake_Pos *ptr = *pipe;
	*pipe = (*pipe)->next;
	free(ptr);
}

void
game_pipe_pos(Snake_Pos **pipe, int *state)
{
	Snake_Pos *ptr = *pipe;
	*state = 0;
	*state += ptr->x + SCREEN_UNIT < 0 ? 1 : 0;
	while(ptr->next != NULL)
		ptr = ptr->next;
	*state += ptr->x < SCREEN_HEIGHT - PIPE_DISTANCE * SCREEN_UNIT ? 2 : 0;
}

/*
 * This function returns the dimension of pipes
 * that snake is currently passing through,
 * so that we can avoid checking all pipes for
 * collision
 *
 * Array elements: x, w, y0, h0, y1, h1
 */
void
game_pipe_bound(Snake_Pos **pipe, int *pos)
{
	Snake_Pos *ptr = *pipe;
	while(ptr != NULL) {
		if (ptr->x < SCREEN_HEIGHT / 2 &&
			ptr->x + SCREEN_UNIT > SCREEN_HEIGHT / 2) {
			pos[0] = ptr->x;
			pos[1] = SCREEN_UNIT;
			pos[2] = 0;
			pos[3] = ptr->y - PIPE_GAP / 2 * SCREEN_UNIT;
			pos[4] = ptr->y + PIPE_GAP / 2 * SCREEN_UNIT;
			pos[5] = SCREEN_HEIGHT - ptr->y - SCREEN_UNIT;
			return;
		}
		ptr = ptr->next;
	}
	pos[0] = 0;
}
