/* 
 * Copyright (C) 2003 Tim Martin
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
 */

#include <stdlib.h>
#include <stdio.h>
#include <string.h>

#include "screen.h"

/* xxx hide this structure */
struct screen_s {
    int x;
    int y;

    int sizex;
    int sizey;


    /* current displayable size of tiles (based upon current zoom) */
    int tilex;
    int tiley;
    int tileheight;

    int dirview; /* view of screen
		  * 0 = 0,0 at top of screen
		  * 1 = 0,max at top
		  */

    point_t *screenmap;
    /*square_t *viewarea;
    int viewsizex;
    int viewsizey;*/

    map_t *map;
    int mapsizex;
    int mapsizey;

};

#define VIEWSPOT(screen, x,y) screen->screenmap[y * screen->sizex + x]

	
int
screen_init(screen_t **ret)
{
    screen_t *screen;

    screen = calloc(1, sizeof(screen_t));
    if (!screen) return -1;

    screen->tilex = 64;
    screen->tiley = 32;
    screen->tileheight = 8;

    *ret = screen;
    return 0;
}

void screen_viewarea_clear(screen_t *screen)
{
    if (!screen->screenmap) {
	printf("viewarea is null!\n");
	return;
    }
    memset(screen->screenmap, -1, sizeof(point_t)*screen->sizex*screen->sizey);
}

void screen_viewarea_set(screen_t *screen, int x, int y, square_t *square)
{
    int i;
    int j;

    /* iterate over polygon setting points */
    for (i = square->parts.topy; i < square->parts.boty; i++) {
	int startx;
	int endx;

	if (i < square->parts.lefty) {
	    float dy1 = -1.0 * ((float)(square->parts.topx - square->parts.leftx))/
		((float)(square->parts.topy - square->parts.lefty));
	    float dy2 = 1.0 * ((float)(square->parts.topx - square->parts.rightx))/
		((float)(square->parts.topy - square->parts.righty));
	    startx = square->parts.leftx + (square->parts.lefty - i) * dy1;
	    endx = square->parts.rightx - (square->parts.righty - i) * dy2;	    

	} else {
	    float dy1 = 1.0 * ((float)(square->parts.botx - square->parts.leftx))/
		((float)(square->parts.boty - square->parts.lefty));
	    float dy2 = -1.0 * ((float)(square->parts.botx - square->parts.rightx))/
		((float)(square->parts.boty - square->parts.righty));
	    startx = square->parts.leftx + (square->parts.lefty - i) * dy1;
	    endx = square->parts.rightx - (square->parts.righty - i) * dy2;	    
	}


	for (j = startx; j < endx; j++) {

	    if ((i >= 0) && (j >= 0) && (i < screen->sizey) && (j < screen->sizex)) {

		int sx = j;
		int sy = i;

		VIEWSPOT(screen,sx,sy).x = x;
		VIEWSPOT(screen,sx,sy).y = y;
	    }
	}
    }
    
    /* xxx */
    /*memcpy(&(VIEWSPOT(screen,x,y)), square, sizeof(square_t));*/
}

void screen_setmap(screen_t *screen, map_t *map)
{
    screen->map = map;
    screen->mapsizex = map_get_sizex(map);
    screen->mapsizey = map_get_sizey(map);
}

int screen_setsize(screen_t *screen, int sizex, int sizey)
{
    if (screen->screenmap) {
	free(screen->screenmap);
    }
    screen->screenmap = malloc(sizeof(point_t) * sizex * sizey);
    screen->sizex = sizex;
    screen->sizey = sizey;
    if (!screen->screenmap) return -1;

    screen_viewarea_clear(screen);

    return 0;
}

#if 0

#define MIN(x,y) (x < y ? x : y)
#define MAX(x,y) (x > y ? x : y) 

static int
point_iswithin_polygon(int x, int y, square_t *sq)
{
  int counter = 0;
  int i;
  double xinters;
  point_t *p1;
  point_t *p2;

  p1 = &sq->points[0];

  for (i = 1; i<= 4; i++ ) {
      p2 = &sq->points[i % 4];
      if (y > MIN(p1->y,p2->y)) {
	  if (y <= MAX(p1->y,p2->y)) {
	      if (x <= MAX(p1->x,p2->x)) {
		  if (p1->y != p2->y) {
		      xinters = (y-p1->y)*(p2->x-p1->x)/(p2->y-p1->y)+p1->x;
		      if (p1->x == p2->x || x <= xinters)
			  counter++;
		  }
	      }
	  }
      }
      p1 = p2;
  }

  if (counter % 2 == 0)
    return 0;
  else
    return 1;
}

#endif /* 0 */

static void
nominal_screen_to_map(screen_t *screen, int sx, int sy, int *mapx, int *mapy)
{
    int sizey = screen->mapsizey - 1;
    int sizex = screen->mapsizex - 1;
    int retx = 0;
    int rety = 0;

    switch (screen->dirview) 
	{
	default:
	case 0:
	    retx =   sx/screen->tilex + sy/screen->tiley - sizey/2;
	    rety = - sx/screen->tilex + sy/screen->tiley + sizey/2;
	    break;
	case 1:
	    retx = - sx/screen->tilex - sy/screen->tiley + sizex/2 + sizey;
	    rety = - sx/screen->tilex + sy/screen->tiley + sizex/2;
	    break;
	case 2:
	    retx = - sx/screen->tilex - sy/screen->tiley + sizex + sizey/2;
	    rety =   sx/screen->tilex - sy/screen->tiley + sizey/2;
	    break;
	case 3:
	    retx =   sx/screen->tilex - sy/screen->tiley + sizex/2;
	    rety =   sx/screen->tilex + sy/screen->tiley - sizex/2;
	    break;
	}

    if (retx < 0) retx = 0;
    if (rety < 0) rety = 0;
    if (retx >= screen->mapsizex) retx = screen->mapsizex-1;
    if (rety >= screen->mapsizey) rety = screen->mapsizey-1;

    *mapx = retx;
    *mapy = rety;
}

int 
screen_map_iterate(screen_t *screen, screen_map_iterator_t *func, void *rock)
{
    int mapsizex = map_get_sizex(screen->map);
    int mapsizey = map_get_sizey(screen->map);
    int xplusy = mapsizex + mapsizey;
    int i;
    int startx;
    int starty;
    int stepx1;
    int stepx2;
    int stepy1;
    int stepy2;
    int xchange;
    int ychange;
    int minx = 0;
    int miny = 0;
    int maxx = mapsizey;
    int maxy = mapsizey;
    int switched;

    switch (screen->dirview)
	{
	default:
	case 0:
	    /* iterate over all the squares backwards */
	    startx = 0;
	    starty = 0;
	    stepx1 = 0;
	    stepx2 = 1;
	    stepy1 = 1;
	    stepy2 = 0;
	    xchange = 1;
	    ychange = -1;

	    nominal_screen_to_map(screen, screen->x + screen->sizex/2, screen->y - screen->sizey/2, &minx, &miny);
	    nominal_screen_to_map(screen, screen->x + screen->sizex/2, screen->y + 3*screen->sizey/2, &maxx, &maxy);
	    startx = minx;
	    starty = miny;
	    xplusy = (maxx - minx) + (maxy - miny);
	    break;


	case 1:
	    /* iterate over all the squares backwards */
	    startx = 0;
	    starty = mapsizey -1;
	    stepx1 = 1;
	    stepx2 = 0;
	    stepy1 = 0;
	    stepy2 = -1;
	    xchange = -1;
	    ychange = -1;
	    nominal_screen_to_map(screen, screen->x + screen->sizex/2, screen->y + 3*screen->sizey/2, &miny, &maxx);
	    nominal_screen_to_map(screen, screen->x + screen->sizex/2, screen->y - screen->sizey/2, &maxy, &minx);
	    nominal_screen_to_map(screen, screen->x + screen->sizex/2, screen->y - screen->sizey/2, &starty, &startx);
	    xplusy = (maxx - minx) + (maxy - miny);
	    /*startx = minx;
	    starty = miny;
	    minx = 0;
	    miny = 0;
	    maxx = mapsizey;
	    maxy = mapsizey;*/
	    break;

	case 2:
	    /* iterate over all the squares backwards */
	    startx = mapsizex - 1;
	    starty = mapsizey - 1;
	    stepx1 = 0;
	    stepx2 = -1;
	    stepy1 = -1;
	    stepy2 = 0;
	    xchange = -1;
	    ychange = 1;

	    nominal_screen_to_map(screen, screen->x + screen->sizex/2, screen->y + 3*screen->sizey/2, &minx, &miny);
	    nominal_screen_to_map(screen, screen->x + screen->sizex/2, screen->y - screen->sizey/2, &maxx, &maxy);
	    nominal_screen_to_map(screen, screen->x + screen->sizex/2, screen->y - screen->sizey/2, &startx, &starty);
	    xplusy = (maxx - minx) + (maxy - miny);
	    break;

	case 3:
	    /* iterate over all the squares backwards */
	    startx = mapsizex - 1;
	    starty = 0;
	    stepx1 = 0;
	    stepx2 = -1;
	    stepy1 = 1;
	    stepy2 = 0;
	    xchange = -1;
	    ychange = -1;

	    nominal_screen_to_map(screen, screen->x + screen->sizex/2, screen->y + 3*screen->sizey/2, &minx, &maxy);
	    nominal_screen_to_map(screen, screen->x + screen->sizex/2, screen->y - screen->sizey/2, &maxx, &miny);
	    nominal_screen_to_map(screen, screen->x + screen->sizex/2, screen->y - screen->sizey/2, &startx, &starty);
	    xplusy = (maxx - minx) + (maxy - miny);
	    break;

	}

    switched = 0;
    for (i = 0; i < xplusy+1; i++) {
	int x = startx;
	int y = starty;

	if (switched == 0) {
	    startx+=stepx1;
	    starty+=stepy1;

	    if (stepx1 > 0) {
		if (startx >= screen->mapsizex-1) switched = 1;
	    } else if (stepx1 < 0) {
		if (startx <= 0) switched = 1;
	    }
	    if (stepy1 > 0) {
		if (starty >= screen->mapsizey-1) switched = 1;
	    } else if (stepy1 < 0) {
		if (starty <= 0) switched = 1;
	    }

	} else {
	    startx+=stepx2;
	    starty+=stepy2;
	}
	/*if ((startx > maxx) && (starty > maxy)) break;*/

	do {
	    if ((x >= minx) && (x <= maxx) && (y >= miny) && (y <= maxy)) {
		if (func(x,y,rock)) {
		    return 1;
		}
	    }
	    
	    x += xchange;
	    y += ychange;
	    
	} while ((x >= 0) && (y>= 0) && (x < mapsizex) && (y < mapsizey));
    }

    return 0;
}


#if 0

struct find_mapcoord_s {
    screen_t *screen;

    int screenx;
    int screeny;

    int found;
    int mapx;
    int mapy;
};

static int
find_mapcoord(int x, int y, void *rock)
{
    struct find_mapcoord_s *fcoord = (struct find_mapcoord_s *)rock;

    if (screen_viewarea_isset(fcoord->screen, x, y)) {
	if (point_iswithin_polygon(fcoord->screenx, fcoord->screeny, 
				   &VIEWSPOT(fcoord->screen,x,y))) {
	    fcoord->found = 1;
	    fcoord->mapx = x;
	    fcoord->mapy = y;
	    return 1;
	}
    }

    return 0;
}

#endif /* 0 */

int
screen_to_mapcoord(screen_t *screen, int screenx, int screeny, int *mapx, int *mapy)
{
    *mapx = -1;
    *mapy = -1;
    if (screenx < 0) {
	screenx = 0;
    }
    if (screeny < 0) {
	screeny = 0;
    } 
    if (screenx >= screen->sizex) {
	screenx = screen->sizex - 1;
    }
    if (screeny >= screen->sizey) {
	screeny = screen->sizey - 1;
    }


    if (!screen->screenmap) {
	printf("viewarea is null!\n");
	return -1;
    }

    *mapx = VIEWSPOT(screen,screenx,screeny).x;
    *mapy = VIEWSPOT(screen,screenx,screeny).y;

    /* 
     * we're off the game board. Let's try to find the closest square 
     */
    if (*mapx == -1) {
	int midx;
	int midy;

	mapcoord_to_screen(screen, screen->mapsizex, screen->mapsizey,
			   screen->mapsizex/2, screen->mapsizey/2, &midx, &midy);

	do {
	    if (screen->x + screenx < midx) {
		screenx++;
		if (screenx >= screen->sizex) return -1;
	    } else if (screen->x + screenx > midx) {
		screenx--;
		if (screenx < 0) return -1;
	    }

	    if (screen->y + screeny < midy) {
		screeny++;
		if (screeny >= screen->sizey) return -1;
	    } else if (screen->y + screeny > midy) {
		screeny--;
		if (screeny < 0) return -1;
	    }

	    *mapx = VIEWSPOT(screen,screenx,screeny).x;
	    *mapy = VIEWSPOT(screen,screenx,screeny).y;

	    /*
	     * Prevent infinite looping
	     */
	    if ((screen->x + screenx == midx) &&
		(screen->y + screeny == midy)) {
		return 0;
	    }

	} while (*mapx == -1);

    }

    return 0;

#if 0
    struct find_mapcoord_s fcoord;

    fcoord.screen = screen;
    fcoord.screenx = screenx;
    fcoord.screeny = screeny;
    fcoord.mapx = -1;    
    fcoord.mapy = -1;
    fcoord.found = 0;

    /* iterate over all the squares backwards */
    screen_map_iterate(screen, &find_mapcoord, &fcoord);

    if (fcoord.found) {
	*mapx = fcoord.mapx;
	*mapy = fcoord.mapy;
	return 0;
    }

    return -1;
#endif /* 0 */
}

int 
screen_to_mapcoord_screen_center(screen_t *screen, int *mapx, int *mapy)
{
    return screen_to_mapcoord(screen, screen->sizex/2, screen->sizey/2, mapx, mapy);
}

int mapcoord_to_screen(screen_t *screen, int mapsizex, int mapsizey, 
		       int mapx, int mapy, int *screenx, int *screeny)
{
    mapsizex--;
    mapsizey--;

    switch (screen->dirview) 
	{
	default:
	case 0:
	    *screenx = (mapsizey + mapx - mapy) * (screen->tilex/2);
	    *screeny = (mapx + mapy) * (screen->tiley/2);
	    break;
	case 1:
	    *screenx = (mapsizex + mapsizey - mapx - mapy) * (screen->tilex/2);
	    *screeny = (mapsizey + mapx - mapy) * (screen->tiley/2);	    
	    break;
	case 2:
	    *screenx = (mapsizex - mapx + mapy) * (screen->tilex/2);
	    *screeny = (mapsizey + mapsizey - mapx - mapy) * (screen->tiley/2);	    	    
	    break;
	case 3:
	    *screenx = (mapx + mapy) * (screen->tilex/2);
	    *screeny = (mapsizex - mapx + mapy) * (screen->tiley/2);	    	    
	    break;
	}

    return 0;
}

/*
 * zoom the viewable size. positive means zoom in. negative means zoom out
 * returns -1 if already at zooming limit
 */
int screen_zoom(screen_t *screen, int zoom)
{
    int mapx;
    int mapy;

    /* remember tile that was in the middle of the screen before */
    screen_to_mapcoord(screen, screen->sizex/2, screen->sizey/2, &mapx, &mapy);

    if (zoom > 0) {
	if (screen->tilex == 128) {
	    return -1;
	}
	screen->tilex *= 2;
	screen->tiley *= 2;
	screen->tileheight *= 2;
    } else {
	if (screen->tilex <= 8) {
	    return -1;
	}
	screen->tilex /= 2;
	screen->tiley /= 2;
	screen->tileheight /= 2;
    }

    /* now re-center the screen */
    if (mapx != -1) {
	int sx;
	int sy;

	mapcoord_to_screen(screen, screen->mapsizex, screen->mapsizey, mapx, mapy, &sx, &sy);

	screen->x = sx - screen->sizex/2;
	screen->y = sy - screen->sizey/2;
    }

    return 0;
}

int screen_getzoom(screen_t *screen)
{
    if (screen->tilex == 128) return 0;
    if (screen->tilex == 64) return 1;
    if (screen->tilex == 32) return 2;
    if (screen->tilex == 16) return 3;
    if (screen->tilex == 8) return 4;
    if (screen->tilex == 4) return 5;
    if (screen->tilex == 2) return 6;

    return -1;
}

void
screen_keep_in_bounds(screen_t *screen, int mapsizex, int mapsizey, int *sx, int *sy)
{
    if (*sx < 0) *sx = 0;
    if (*sx > screen->tilex/2 * (mapsizex + mapsizey)) 
	*sx = screen->tilex/2 * (mapsizex + mapsizey);

    if (*sy < 0) *sy = 0;
    if (*sy > screen->tiley/2 * (mapsizex + mapsizey))
	*sy = screen->tiley/2 * (mapsizex + mapsizey);
}


int
screen_is_square_viewable(screen_t *screen, int mapx, int mapy, int *screenx, int *screeny)
{
    /* translate map coordinates to screen coordinates */
    mapcoord_to_screen(screen, screen->mapsizex, screen->mapsizey,
		       mapx, mapy, screenx, screeny);

    if ((*screenx + screen->tilex >= screen->x) && (*screenx <= screen->x + screen->sizex) && 
	(*screeny + screen->tiley >= screen->y) && (*screeny - 8 * screen->tileheight <= screen->y + screen->sizey)) {

	(*screenx)-=screen->x;
	(*screeny)-=screen->y;

	return 1;
    } else {
	return 0;
    }
}

void 
screen_set_position(screen_t *screen, int x, int y)
{
    screen->x = x;
    screen->y = y;
}

void
screen_rotate(screen_t *screen, int offset)
{
    screen->dirview+=offset;
    screen->dirview = (screen->dirview+4)%4;
}

int
screen_getdirview(screen_t *screen)
{
    return screen->dirview;
}

void
screen_getdirviewadds(screen_t *screen, int *x, int *y)
{
    switch (screen->dirview) 
	{
	case 0:
	    *x = 1;
	    *y = 1;
	    break;
	case 1:
	    *x = -1;
	    *y = 1;
	    break;
	case 2:
	    *x = -1;
	    *y = -1;
	case 3:
	    *x = 1;
	    *y = -1;
	    break;
	}
}

void
screen_tilesizes(screen_t *screen, int *sx, int *sy, int *th)
{
    *sx = screen->tilex;
    *sy = screen->tiley;
    *th = screen->tileheight;
}

int
screen_tileheight(screen_t *screen)
{
    return screen->tileheight;
}
