/* 
 * 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 <string.h>
#include <stdio.h>

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>

#include <unistd.h>

#include <netinet/in.h>
#include <netdb.h>
#include <sys/socket.h>
#include <sys/file.h>
#include <netinet/in.h>
#include <netdb.h>

#include "client.h"
#include "callbacks.h"
#include "utils.h"

struct client_s {
    int sock;
    FILE *stream;

    tiles_t *tiles;
    map_t *map;
    player_t *me;
    player_t *players[5];
    alert_callback_t *alert_func;
    void *alert_rock;
    popreport_callback_t *popreport_func;
    void *popreport_rock;
    finances_callback_t *finances_func;
    void *finances_rock;
    finances_loan_callback_t *finances_loan_func;
    void *finances_loan_rock;
    spotinfo_callback_t *spotinfo_func;
    void *spotinfo_rock;
    gameinfo_callback_t *listgames_func;
    void *listgames_rock;
    stats_callback_t *stats_func;
    void *stats_rock;

    char *value_string;
    int tax_rate;

    char curline[4096*4];
    int lineoffset;
};

/* forward declarations */
static void read_and_parse(client_t *client, int sync, int *didsomething);

static void
client_write(client_t *client, int flush, char *msg, ...)
{
    char str[4096];
    va_list ap;
    va_start(ap, msg);

    vsnprintf(str, sizeof(str)-1, msg, ap);

    fprintf(client->stream,"%s", str);
    if (flush) {
	if (fflush(client->stream)) {
	    printf(_("Flush error: %s\n"), strerror(errno));
	}
    }

    va_end(ap);
}

static int
player_login(client_t *client, const char *playername)
{
    int i;

    client_write(client, 1, "LOGIN %s\r\n", playername);

    for (i = 0; i < 4; i++) {
	player_new("foobar", &client->players[i+1]);
    }

    read_and_parse(client, 1, NULL);

    return 0;
}

int client_init(const char *server, short port, const char *playername, 
		alert_callback_t *alert_cb, void *alert_rock,
		client_t **client)
{
  struct sockaddr_in addr;
  struct hostent *hp;
  int sock;
  client_t *ret;

  ret = calloc(sizeof(client_t), 1);
  if (!ret) return -1;

  if ((hp = gethostbyname(server)) == NULL) {
      printf("gethostbyname for %s:%d", server, port);
    perror("gethostbyname");
    return -1;
  }

  if ((sock = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
    perror("socket");
    return -1;
  }

  addr.sin_family = AF_INET;
  memcpy(&addr.sin_addr, hp->h_addr, hp->h_length);
  addr.sin_port = htons(port);

  if (connect(sock, (struct sockaddr *) &addr, sizeof (addr)) < 0) {
    perror("connect");
    return -1;
  }

  ret->sock = sock;
  ret->stream = fdopen(sock, "w+");

  ret->alert_func = alert_cb;
  ret->alert_rock = alert_rock;

  player_login(ret, playername);
 
  *client = ret;

  return 0;
}

#define MAX_ARGS 100

/*
 * Make into a args structure
 */
static int
make_args(client_t *client, char *str, char **argv, int maxargs, int *argc)
{
    int numargs = 0;

    while ((str) && (*str != '\0')) {
	char *end;
	char *p = str;
	int inquotes = 0;

	if (*p == '{') {
	    int len = atoi(p+1);
	    char *data = malloc(len+1);

	    fread(data, 1, len, client->stream);
	    data[len] = '\0';

	    argv[numargs++] = data;
	    break;
	}
	
	while ((*p != '\0') && ((inquotes) || (*p != ' '))) {
	    if (*p == '"') {
		inquotes = (inquotes + 1) % 2;
	    }

	    p++;
	}
	end = p;

	if (*end != '\0') {
	    *end = '\0';
	    end++;
	}

	argv[numargs++] = str;
	
	str = end;
    }

    *argc = numargs;
    argv[numargs] = NULL;

    return 0;
}

static void
read_quad(char *str, int *quad)
{
    char *next;
    int num;

    num = strtol(str, &next, 10);
    if (*next != ',') return;
    quad[RANGE_CURRENT_MONTH] = num;

    num = strtol(next+1, &next, 10);
    if (*next != ',') return;
    quad[RANGE_LAST_MONTH] = num;

    num = strtol(next+1, &next, 10);
    if (*next != ',') return;
    quad[RANGE_CURRENT_YEAR] = num;

    num = strtol(next+1, &next, 10);
    if (*next != ',') return;
    quad[RANGE_LAST_YEAR] = num;

    num = strtol(next+1, &next, 10);
    if (*next != '\0') {
	return;
    }
    quad[RANGE_ALLTIME] = num;    
}

static void
parse_finances(client_t *client, char **argv, int argc)
{
    int i;
    int total[RANGE_NUMRANGES];
    int total_recurring[RANGE_NUMRANGES];

    for (i = 0; i < RANGE_NUMRANGES; i++) {
	total[i] = 0;
	total_recurring[i] = 0;
    }

    if (!client->finances_func) {
	return;
    }

    for (i = 0; i < argc; i++) {
	int quad[RANGE_NUMRANGES];
	int j;
	char *str;

	for (j = 0; j < RANGE_NUMRANGES; j++) {
	    quad[j] = 0;
	}

	str = strchr(argv[i],'=');
	if (str) {
	    *str = '\0';
	    read_quad(str+1, quad);

	    client->finances_func(argv[i], quad, client->finances_rock);

	    if (!is_one_time(argv[i])) {
		for (j = 0; j < RANGE_NUMRANGES; j++) {
		    total_recurring[j] += quad[j];
		}
	    }

	    for (j = 0; j < RANGE_NUMRANGES; j++) {
		total[j] += quad[j];
	    }
	    	    
	} else {
	    printf("Parse error: %s\n", argv[i]);
	}
	
    }

    client->finances_func("-", 0, client->finances_rock);
    client->finances_func(_("Total"), total, client->finances_rock);
    client->finances_func(_("Total (excluding one time charges)"), total_recurring, 
			  client->finances_rock);
}

static void
parse_loan(client_t *client, char **argv, int argc)
{
    int amount;
    int term;
    int payments;
    float rate;
    char str[256];
    char amountstr[256];
    int num;

    if (argc < 5) {
	printf("Wrong number of arguments for LOAN\n");
	return;
    }

    num = atoi(argv[0]);
    amount = atoi(argv[1]);
    term = atoi(argv[2]);
    payments = atoi(argv[3]);
    rate = atof(argv[4]);

    prettyprint_money(amount, amountstr, sizeof(amountstr)-1);

    snprintf(str, sizeof(str)-1, _("%s at %.02f - %d%% paid off"), amountstr,
	     rate, (payments*100/term));

    client->finances_loan_func(num, str, client->finances_loan_rock);
}

static void
parse_population(client_t *client, char **argv, int argc)
{
    int age;
    int edlevel;
    int size = 0;
    int income = 0;
    int housed = 0;
    int work = 0;
    int happy = 0;
    int i;

    if (argc < 2) {
	printf("Wrong number of arguments for POPULATION\n");
	return;
    }

    age = atoi(argv[0]);
    edlevel = atoi(argv[1]);
    
    for (i = 2; i < argc; i++) {

	if (strncasecmp(argv[i],"SIZE=",5) == 0) {
	    size = atoi(argv[i]+5);	    
	} else if (strncasecmp(argv[i],"INCOME=",7) == 0) {
	    income = atoi(argv[i]+7);	    
	} else if (strncasecmp(argv[i],"HAPPINESS=",10) == 0) {
	    happy = atoi(argv[i]+10);	    
	} else if (strncasecmp(argv[i],"HOUSED=",7) == 0) {
	    housed = atoi(argv[i]+7);	    
	} else if (strncasecmp(argv[i],"WORKING=",8) == 0) {
	    work = atoi(argv[i]+8);	    
	}
    }

    if (client->popreport_func) {
	client->popreport_func(age, edlevel, size, income, housed, work, happy, client->popreport_rock);
    } else {
	printf("No popreport specified!\n");
    }
}

static void
parse_spotinfo(client_t *client, char **argv, int argc)
{
    int i;

    for (i = 0; i < argc; i++) {
	char *name;
	char *value;

	name = argv[i];

	value = strchr(name, '=');
	if (value) {
	    *value = '\0';
	    value++;

	    client->spotinfo_func(name, value, client->spotinfo_rock);
	}
    }
}


static void
parse_data(client_t *client, char *line, int *changedmap)
{
    char *space;
    char *argv[MAX_ARGS+1];
    int argc = 0;

    space = strchr(line,' ');
    if (space) {
	*space = '\0';
	space++;

	make_args(client, space, argv, MAX_ARGS, &argc);
    }

    if (strcasecmp(line,"BOARDSIZE") == 0) {
	int r;

	if (argc != 2) {
	    printf("boardsize requires 2 arguments!\n");
	    return;
	}

	r = map_init(client->tiles, atoi(argv[0]), atoi(argv[1]), map_item_emptytype(), &client->map);
	if (r) {
	    printf("Error making map\n");
	}

	if (changedmap) *changedmap = 1;

    } else if ((strcasecmp(line,"BOARDLINE") == 0) && (client->map)) {
	int x;
	int y;
	int mapsizex;

	if (argc != 2) {
	    printf("boardline requires 2 arguments!\n");
	    return;
	}
	
	y = atoi(argv[0]);
	mapsizex = map_get_sizex(client->map);
	
	for (x = 0; x < mapsizex; x++) {
	    mapspot_t spot;

	    map_string2spot(&argv[1][4*x], &spot);

	    if (map_set_spot(client->map, NULL, x, y, 0, 1, &spot)) {
		printf("Error setting spot at %d,%d\n", x, y);
	    }
	}

	if (changedmap) *changedmap = 1;

    } else if ((strcasecmp(line,"BOARDSPOT") == 0) && (client->map)) {
	int x;
	int y;
	mapspot_t spot;

	if (argc != 3) {
	    printf("boardline requires 3 arguments!\n");
	    return;
	}
	
	x = atoi(argv[0]);
	y = atoi(argv[1]);

	map_string2spot(argv[2], &spot);

	map_set_spot(client->map, NULL, x, y, 0, 1, &spot);

	if (changedmap) *changedmap = 1;
	
    } else if (strcasecmp(line,"PLAYERINFO") == 0) {
	char *name = argv[0];
	char *value = argv[1];

	if (strcasecmp(name,"NUMBER") == 0) {
	    client->me = client->players[atoi(value)];
	    player_setnumber(client->me, atoi(value));
	} else if (strcasecmp(name,"CASH") == 0) {
	    player_setmoney(client->me, atoi(value));
	    updatemoney();
	} else {
	    printf("Unknown playerinfo thing: %s\n",name);
	}

    } else if (strcasecmp(line,"CURTIME") == 0) {
	time_t curtime = atoi(argv[0]);

	set_current_time(curtime);

    } else if (strcasecmp(line,"WEATHER") == 0) {
	int temp = atoi(argv[0]);

	set_current_weather(argv[1], temp);

    } else if (strcasecmp(line,"SPEED") == 0) {
	int speed = atoi(argv[0]);
	
	set_speed_ui(speed);

    } else if (strcasecmp(line,"POPULATION") == 0) {
	parse_population(client, argv, argc);

    } else if (strcasecmp(line,"FINANCES") == 0) {
	parse_finances(client, argv, argc);

    } else if (strcasecmp(line,"LOAN") == 0) {
	parse_loan(client, argv, argc);

    } else if (strcasecmp(line,"SPOTINFO") == 0) {
	parse_spotinfo(client, argv, argc);

    } else if (strcasecmp(line,"REPOSITION") == 0) {
	int x;
	int y;

	if (argc != 2) {
	    printf("reposition requires 2 arguments!\n");
	    return;
	}

	x = atoi(argv[0]);
	y = atoi(argv[1]);

	screen_center_on_xy(client->map, x, y);

    } else if (strcasecmp(line,"VALUE") == 0) {

	client->value_string = strdup(argv[0]);

    } else if (strcasecmp(line,"TAXRATE") == 0) {

	client->tax_rate = atoi(argv[0]);

    } else if (strcasecmp(line,"GAMEOVER") == 0) {
	client->alert_func(argv[0], client->alert_rock);

    } else if (strcasecmp(line,"GAME") == 0) {
	char *name = argv[0];
	
	if (client->listgames_func) {
	    client->listgames_func(name, client->listgames_rock);
	}

    } else if (strcasecmp(line,"STAT") == 0) {
	char *name = argv[0];
	char *value = argv[1];
	
	if (client->stats_func) {
	    client->stats_func(name, value, client->stats_rock);
	}

    } else {
	printf("command not understood! '%s'\n",line);
    }
}

static int
selectone(int fd)
{
    return 0;


    /*
     * This messes up the FILE *
     */
#if 0
     
    fd_set readfds;
    struct timeval timeout;
    int r;

    return 0;

    
    timeout.tv_sec = 0;
    timeout.tv_usec = 0;

    FD_ZERO(&readfds);
    FD_SET(fd, &readfds);

    r = select(fd+1, &readfds, NULL, NULL, &timeout);

    if (r > 0) {
	return 1;
    } else {
	return 0;
    }
#endif /* 0 */
}

static void
read_and_parse(client_t *client, int sync, int *changedmap)
{
    char *line;

    while (1) {
	int len;

	if ((!sync) && (!selectone(client->sock))) {
	    return;
	}

	if (!fgets(client->curline + client->lineoffset, 
		   sizeof(client->curline)-1 - client->lineoffset,
		   client->stream)) {
	    continue;
	}

	len = strlen(client->curline);
	if (client->curline[len-1] != '\n') {
	    client->lineoffset = len;
	    continue;
	}
	client->lineoffset = 0;

	line = client->curline;

	/* remove any newline */
	if (line[len-1] == '\n') {
	    len--;
	}
	if (line[len-1] == '\r') {
	    len--;
	}
	line[len] = '\0';

	if (*line == '*') {	    
	    parse_data(client, line+2, changedmap);	    

	} else if (strncasecmp(line,"OK",2) == 0) {
	    return;

	} else if (strncasecmp(line,"NO",2) == 0) {
	    printf("ERROR! %s\n",line+3);
	    return;
	} else {
	    printf("Unexpected! %s\n",line);
	}
    }

    printf("fgets returned NULL\n");
}

map_t *client_getmap(client_t *client, tiles_t *tiles)
{
    if (client->map) {
	return client->map;
    }

    client_write(client, 1, "REFRESHBOARD\r\n");
    client->tiles = tiles;

    read_and_parse(client, 1, NULL);

    return client->map;
}

map_t *client_newmap(client_t *client, tiles_t *tiles, int sizex, int sizey)
{
    client_write(client, 1, "NEWBOARD %d %d\r\n",sizex, sizey);

    client->tiles = tiles;

    read_and_parse(client, 1, NULL);

    return client->map;
}

int
client_txn_start(client_t *client)
{
    client_write(client, 1, "TXN_START\r\n");

    read_and_parse(client, 1, NULL);

    return 0;
}

int
client_txn_commit(client_t *client)
{
    client_write(client, 1, "TXN_COMMIT\r\n");

    read_and_parse(client, 1, NULL);

    return 0;
}

int
client_setspot(client_t *client, int x, int y, mapobj_t mapobj)
{
    mapspot_t spot;
    
    memset(&spot, 255, sizeof(mapspot_t));
    spot.mapobj = mapobj;

    client_write(client, 1, "SET %d %d %s\r\n",x, y, map_spot2string(&spot));

    read_and_parse(client, 1, NULL);

    return 0;
}

int client_changeheight(client_t *client, mapspot_list_t *list)
{
    mapspot_list_t *sentlist = NULL;

    while (list) {
	int x;
	int y;
	mapspot_t spot;
	int change;

	mapspot_list_extract(&list, &x, &y, (void **)&change);

	map_get_spot(client->map, x, y, &spot);
	spot.height += (mapspot_list_num_within(sentlist, x, y, 0.0) + 1) * change;

	client_write(client, 1, "SET %d %d %s\r\n", x, y, map_spot2string(&spot));
	read_and_parse(client, 1, NULL);	

	mapspot_list_add(&sentlist, x, y, NULL);
    }

    mapspot_list_free(sentlist);

    return 0;
}

void client_checkpoint(client_t *client, int *mapchanged)
{
    if (!client) return;

    client_write(client, 1, "NOOP\r\n");

    read_and_parse(client, 1, mapchanged);
}

int client_buyland(client_t *client, int x, int y)
{
    mapspot_t spot;
    
    memset(&spot, 255, sizeof(mapspot_t));
    spot.owner = player_getnum(client->me);

    client_write(client, 1, "SET %d %d %s\r\n",x, y, map_spot2string(&spot));

    read_and_parse(client, 1, NULL);

    return 0;
}

int client_sellland(client_t *client, int x, int y)
{
    mapspot_t spot;
    
    memset(&spot, 255, sizeof(mapspot_t));
    spot.owner = NO_OWNER;

    client_write(client, 1, "SET %d %d %s\r\n",x, y, map_spot2string(&spot));

    read_and_parse(client, 1, NULL);

    return 0;
}

player_t *client_getme(client_t *client)
{
    return client->me;
}

int 
client_getpopreport(client_t *client, popreport_callback_t *func, void *rock)
{
    client->popreport_func = func;
    client->popreport_rock = rock;

    client_write(client, 1, "POPULATIONSTATS\r\n");

    read_and_parse(client, 1, NULL);

    client->popreport_func = NULL;
    client->popreport_rock = NULL;

    return 0;    
}

int 
client_getfinances(client_t *client, finances_callback_t *func, void *rock,
		   finances_loan_callback_t *loan_func, void *loan_rock)
{
    client->finances_func = func;
    client->finances_rock = rock;
    client->finances_loan_func = loan_func;
    client->finances_loan_rock = loan_rock;

    client_write(client, 1, "FINANCES\r\n");

    read_and_parse(client, 1, NULL);

    client->finances_func = NULL;
    client->finances_rock = NULL;

    return 0;    
}

int 
client_setspeed(client_t *client, game_speeds_t speed)
{
    client_write(client, 1, "SETSPEED %d\r\n",speed);

    read_and_parse(client, 1, NULL);

    return 0;
}

int 
client_getspot_info(client_t *client, int x, int y, spotinfo_callback_t *func, void *rock)
{
    client->spotinfo_func = func;
    client->spotinfo_rock = rock;

    client_write(client, 1, "SPOTINFO %d %d\r\n", x, y);

    read_and_parse(client, 1, NULL);

    client->spotinfo_func = NULL;
    client->spotinfo_rock = NULL;

    return 0;    
}

int
client_borrow(client_t *client, int amount)
{
    client_write(client, 1, "BORROW %d\r\n", amount);

    read_and_parse(client, 1, NULL);

    return 0;
}

int
client_listgames(client_t *client, gameinfo_callback_t *cb, void *rock)
{
    client->listgames_func = cb;
    client->listgames_rock = rock;

    client_write(client, 1, "LISTGAMES\r\n");

    read_and_parse(client, 1, NULL);

    client->listgames_func = NULL;
    client->listgames_rock = NULL;

    return 0;        
}

int
client_getstats(client_t *client, stats_callback_t *cb, void *rock)
{
    client->stats_func = cb;
    client->stats_rock = rock;

    client_write(client, 1, "STATS\r\n");

    read_and_parse(client, 1, NULL);

    client->stats_func = NULL;
    client->stats_rock = NULL;

    return 0;        
}

char *
client_getgoal(client_t *client)
{
    client_write(client, 1, "GETGOAL\r\n");

    read_and_parse(client, 1, NULL);

    return client->value_string;
}

char *
client_gethappiness(client_t *client)
{
    client_write(client, 1, "GETHAPPINESS\r\n");

    read_and_parse(client, 1, NULL);

    return client->value_string;
}

int
client_selectgame(client_t *client, const char *name)
{
    client_write(client, 1, "SELECTGAME %s\r\n", name);

    read_and_parse(client, 1, NULL);

    return 0;    
}

void
client_close(client_t *client)
{
    fclose(client->stream);
    close(client->sock);
}

void
client_free(client_t *client)
{
    free(client);
}

int
client_payoff_loan(client_t *client, int num)
{
    client_write(client, 1, "PAYBACK %d\r\n", num);

    read_and_parse(client, 1, NULL);

    return 0;    
}

int
client_gettaxrate(client_t *client, char *name)
{
    client_write(client, 1, "GETTAX %s\r\n", name);

    read_and_parse(client, 1, NULL);

    return client->tax_rate;
}

void client_settaxrate(client_t *client, char *name, int rate)
{
    client_write(client, 1, "SETTAX %s %d\r\n", name, rate);

    read_and_parse(client, 1, NULL);

}
