#include <6170.h>
#include <chess_display_board.h>
#include <ps5/url_ops.h>
#include <ps5/error_response.h>
#include <ctype.h>
#include <chess_capture_move.h>
#include <player.h>

typeinfo_t chess_display_board::typeinfo = new_typeinfo();

chess_display_board::~chess_display_board() {
}

chess_display_board::chess_display_board(istream &initial_format) :brd(8) {
    char ch;
    int x;
    square insertion;
    column temp(8);

    for(x=0;x<8;x++){
	brd.append(temp);
    }
    
    initialization_success= true;
    x=63;
    while((x>=0) && (initial_format.good()) && (initial_format >> ch)) {
	insertion= ctosquare(ch);
	if (insertion.piece_color==white)
	    white_pieces++;
	else if (insertion.piece_color==black)
	    black_pieces++;
	
	if ((x / 8 + x) % 2)
	    insertion.square_color = black;
	else
	    insertion.square_color = white;
	if (insertion.piece_type == invalid) {
	    initialization_success = false;
	    break;
	}
	insertion.square_type = normal;
	brd[x / 8].append(insertion);
	x--;
    }
    
    if (x != -1)
	initialization_success = false;
    
    if (!initialization_success){
	cerr << "Invalid board format\n";
	for(x=0;x<8;x++)
	    brd[x].clear();
	black_pieces = 0;
	white_pieces = 0;
	for(x=0;x<64;x++){
	    if ((x / 8 + x) % 2)
		insertion.square_color = black;
	    else
		insertion.square_color = white;
	    insertion.piece_type = none;
	    insertion.piece_color = blank;
	    insertion.square_type = normal;
	    brd[x / 8].append(insertion);
	}
    }
    total_pieces = white_pieces + black_pieces;
    player_turn = 0;
    brd_state_changed = true;
    square_selected = false;
}

chess_display_board::chess_display_board() : brd (8) {
    int x;
    square insertion;
    column temp(8);

    for(x=0;x<8;x++){
	brd.append(temp);
    }
    for(x=0;x<64;x++){
	if (x % 2)
	    insertion.square_color = black;
	else
	    insertion.square_color = white;
	insertion.piece_type = none;
	insertion.piece_color = blank;
	insertion.square_type = normal;
	brd[x / 8].append(insertion);
    }
    initialization_success= true;
    player_turn = 0;
    brd_state_changed = true;
    square_selected = false;
}

void chess_display_board::clone_part(chess_display_board &cb) const {
    cb.initialization_success = initialization_success;
    cb.brd_state_changed = brd_state_changed;
    cb.white_pieces = white_pieces;
    cb.black_pieces = black_pieces;
    cb.player_turn = player_turn;
    cb.square_selected = square_selected;
    cb.sel_col = sel_col;
    cb.sel_row = sel_row;
    cb.brd = brd;
}

int chess_display_board::pieces_remaining(int player_num) const{
    if (player_num==0)
	return white_pieces;
    else if (player_num==1)
	return black_pieces;
    else
	return -1;
}

void chess_display_board::place_piece(int x, int y, chess_piece piece, 
				      color piece_color) {
    check(((x>=0) && (x<=7)),"place_piece: x out of range");
    check(((y>=0) && (y<=7)),"place_piece: y out of range");
    square temp = brd[y][x];
    check(temp.piece_type == none,"place_piece: square isn't empty");
    temp.piece_type = piece;
    temp.piece_color = piece_color;
    if (piece_color==white)
	white_pieces++;
    else if (piece_color==black)
	black_pieces++;
    brd[y][x] = temp;
    brd_state_changed = true;
}

void chess_display_board::remove_piece(int x, int y){
    check((x>=0) && (x<=7),"remove_piece: x out of range");
    check((y>=0) && (y<=7),"remove_piece: y out of range");
    square temp = brd[y][x];
    check(temp.piece_type != none,"remove_piece: square is empty");
    if (temp.piece_color==white)
	white_pieces--;
    else if (temp.piece_color==black)
	black_pieces--;
    temp.piece_type = none;
    temp.piece_color = blank;
    brd[y][x] = temp;
    brd_state_changed = true;
}

void chess_display_board::move_piece(int x1, int y1, int x2, int y2) {
    check((x1>=0) && (x1<=7),"move_piece: x out of range");
    check((y1>=0) && (y1<=7),"move_piece: y out of range");
    check((x2>=0) && (x2<=7),"move_piece: x out of range");
    check((y2>=0) && (y2<=7),"move_piece: y out of range");
    square sq1 = brd[y1][x1];
    check(sq1.piece_type != none,"move_piece: source square is empty");
    brd[y1][x1].piece_type = none;
    brd[y1][x1].piece_color = blank;
    square sq2 = brd[y2][x2];
    if (sq2.piece_type != none){
	if (sq2.piece_color==white)
	    white_pieces--;
	else if (sq2.piece_color==black)
	    black_pieces--;
    }	
    if (((y2==0) || (y2==7)) && (sq1.piece_type == pawn))
	brd[y2][x2].piece_type = queen;
    else
	brd[y2][x2].piece_type = sq1.piece_type;
    brd[y2][x2].piece_color = sq1.piece_color;
    player_turn = !player_turn;  // Because players are 0 and 1
    brd_state_changed = true;
    square_selected = false;
}

chess_display_board::square chess_display_board::ctosquare(char ch) const {
    square temp;
    
    switch(ch){
    case '-': temp.piece_type = none;
	temp.piece_color = blank;
	break;
    case 'p': temp.piece_type = pawn;
	temp.piece_color = black;
	break;	
    case 'r': temp.piece_type = rook;
	temp.piece_color = black;
	break;
    case 'n': temp.piece_type = knight;
	temp.piece_color = black;
	break;
    case 'b': temp.piece_type = bishop;
	temp.piece_color = black;
	break;
    case 'q': temp.piece_type = queen;
	temp.piece_color = black;
	break;
    case 'k': temp.piece_type = king;
	temp.piece_color = black;
	break;
    case 'P': temp.piece_type = pawn;
	temp.piece_color = white;
	break;
    case 'R': temp.piece_type = rook;
	temp.piece_color = white;
	break;
    case 'N': temp.piece_type = knight;
	temp.piece_color = white;
	break;
    case 'B': temp.piece_type = bishop;
	temp.piece_color = white;
	break;
    case 'Q': temp.piece_type = queen;
	temp.piece_color = white;
	break;
    case 'K': temp.piece_type = king;
	temp.piece_color = white;
	break;
    default: temp.piece_type = invalid;
	temp.piece_color = blank;
    }
    return temp;
}

char chess_display_board::squaretoc(square sq) const {
    switch(sq.piece_type){
    case none:
	return '-';
    case invalid:         // should never happen
	return '-';
    case pawn:
	if (sq.piece_color == black)
	    return 'p';
	else
	    return 'P';
    case rook:
	if (sq.piece_color == black)
	    return 'r';
	else
	    return 'R';
    case knight:
	if (sq.piece_color == black)
	    return 'n';
	else
	    return 'N';
    case bishop:
	if (sq.piece_color == black)
	    return 'b';
	else
	    return 'B';
    case queen:
	if (sq.piece_color == black)
	    return 'q';
	else
	    return 'Q';
    case king:
	if (sq.piece_color == black)
	    return 'k';
	else
	    return 'K';
    }
    return '-'; // default to turn off warning
}

void * chess_display_board::force (typeinfo_t &t) {
    if (t == typeinfo) return this;
    else return 0;
}

void chess_display_board::set_square(int x, int y, color square_color, 
				     square_t type) {
    check((x>=0) && (x<=7),"set_square: x out of range");
    check((y>=0) && (y<=7),"set_square: y out of range");    
    brd[y][x].square_color = square_color;
    brd[y][x].square_type = type;
    brd_state_changed = true;
}


void chess_display_board::save(ostream &out) const {
    int x,y;
    if (current_player() == 0)
	out <<"W\n";
    else out << "B\n";
    out<<get_game()->get_player(0)->get_time();
    out << " "<<get_game()->get_player(1)->get_time()<<endl;
    
    for(y=7;y>=0;y--){
	for(x=0;x<8;x++)
	    out << squaretoc(brd[y][x]);
	out << "\n";
    }    
}



bool chess_display_board::move_validity(int player_num, path &p, 
					chess_piece piece_taken) const {
    int x,xdiff, ydiff;
    int x_sign=1, y_sign=1;
    int dir;
        
    int x1 = p.x1;
    int x2 = p.x2;
    int y1 = p.y1;
    int y2 = p.y2;
    
    if ((x1<0)||(x1>7)||(x2<0)||(x2>7)||(y1<0)||(y1>7)||(y2<0)||(y2>7))
	return false;
    
    if ((x1==x2) && (y1==y2))
	return false;
    
    square from = brd[y1][x1];
    square to = brd[y2][x2];
    color c;
    
    if (player_num == 0){
	c = white;
	dir =1;
    }
    else{
	c = black;
	dir = -1;
    }	

    if ((from.piece_color != c) || (to.piece_color == c)|| 
	(piece_taken != to.piece_type))
	return false;
    
    xdiff = x2-x1;
    if (xdiff<0){
	x_sign = -1;
	xdiff = -xdiff;
    }
    
    ydiff = y2-y1;
    if (ydiff < 0){
	y_sign = -1;
	ydiff = -ydiff;
    }
    
    switch(from.piece_type){
    case invalid:  return false;  //this should never happen
    case none:     return false;
    case pawn:
	// Check for 1 forward, not capture
	if ((y2 == y1 +dir) && (x1 == x2)) 
	    if (brd[y1+dir][x1].piece_type == none)
		return true;
		
	// Check for moving 2 forward
	// There was already a check that x2,y2 was legal place to be
	if (y2 == y1+2*dir)
	    if ((x1 == x2) &&
		(to.piece_type == none) &&
		(brd[y1+dir][x2].piece_type == none))
		return true;
		
	// Check for captures
	if ((x2 == x1+1) || (x2==x1-1))
	    if (y2 == y1 + dir)
		if (to.piece_type != none)
		    return true;
	
	return false;
	break;
	
    case knight:
	if (((xdiff == 1) && (ydiff == 2)) ||
	    ((xdiff == 2) && (ydiff == 1)))
		return true;

	return false;
	break;
	
    case queen:
	if (xdiff == ydiff) {
	    for(x=1;x<xdiff-1;x++)
		if (brd[y1+x*y_sign][x1+x*x_sign].piece_type != none)
		    return false;
	    return true;
	}	
	else if ((!xdiff && ydiff) ||
		 (!ydiff && xdiff)) {
	    if (xdiff){
		for(x=1;x<xdiff-1;x++)
		    if (brd[y2][x1+x*x_sign].piece_type != none)
			return false;
	    }
	    else{
		for(x=1;x<ydiff-1;x++)
		    if (brd[y1+x*y_sign][x1].piece_type != none)
			return false;
	    }
	    return true;
	}
	return false;
	break;
	
    case bishop:
	if (xdiff != ydiff)
	    return false;
	
	for(x=1;x<xdiff-1;x++)
	    if (brd[y1+x*y_sign][x1+x*x_sign].piece_type != none)
		return false;
	return true;
	break;
	
    case rook:
	if (!((!xdiff && ydiff) || (!ydiff && xdiff)))
	    return false;
	
	if (xdiff){
	    for(x=1;x<xdiff-1;x++)
		if (brd[y2][x1+x*x_sign].piece_type != none)
		    return false;
	}
	else{
	    for(x=1;x<ydiff-1;x++)
		if (brd[y1+x*y_sign][x1].piece_type != none)
		    return false;
	}
	return true;
	break;
	
    case king:
	if ((xdiff >1) || (ydiff > 1))
	    return false;

	return true;
	break;
    }
    return false;
}


http_response * chess_display_board::get (string request, extensions *ext)
{
if (!is_associated())
    return new error_response(http_response::INTERNAL_ERROR,"Tried sending request to unassociated board--this is impossible!");

    string first ;
    if (unescape_uri(first_component(request),first) ) {
	if (first == "move") {
	    map <string,string> *q = parse_query(extract_query(request));
	    string sq1,sq2;
	    int x1,y1,x2,y2;
	    
	    if (q&&q->find("from", sq1)&&q->find("to",sq2)
		&&parse_square(sq1,y1,x1)&&parse_square(sq2,y2,x2)) {
		chess_capture_move m(x1, y1, x2, y2, this);
		string player_num_s;
		if (q&&!q->find("player", player_num_s))
		    return new error_response(http_response::BAD_REQUEST,"Requests to move must include a player number.");
		int pnum = atoi(player_num_s.get_chars());
		if (0 <= pnum < get_game()->players()) {
		    switch(get_game()->apply(m,pnum)) {
			    case game::move_invalid:
				get_game()->message(get_game()->get_player(pnum)->get_name()+
						    " made an invalid move.");
				break;
			    case game::game_already_done:
				get_game()->message("You can move after the game is over!");
				break;
		    default:
		    break;}	// OK is fine
		    
		    
		    return get_game()->display(ext, pnum);
		}
		else return new error_response(http_response::BAD_REQUEST,"Player number out of range");
	    }
	    else return new error_response(http_response::BAD_REQUEST, "Improper move specification.");
	}
	else if (first == "select") {
	    map <string, string> *q = parse_query(extract_query(request));
	    if  (!(q->contains("player")&&q->contains("square"))) 
		return new error_response(http_response::BAD_REQUEST, "A selection request must contain botha square and player");
	    int player_num = atoi(q->fetch("player").get_chars());
	    int row,column;
	    if (parse_square(q->fetch("square"),row,column)) {
		if ((player_num < 0)||(player_num > get_game()->players()))
		    return new error_response(http_response::BAD_REQUEST,"Player number out of range.");
		if (player_num == current_player()) {
		    square_selected = true;
		    sel_row = row;
		    sel_col = column;
		}
		else get_game()->message(get_game()->get_player(player_num)->
				       get_name()+" selected a square out of turn.");
		return get_game()->display(ext,player_num);
	    }
	    else return new error_response(http_response::BAD_REQUEST, "Couldn't find square and player number.");
	}
	else if (first == "dest") 
	    {
		map <string, string> *q = parse_query(extract_query(request));
		if  (!(q&&q->contains("player")&&q->contains("square"))) 
		    return new error_response(http_response::BAD_REQUEST, "A selection request must contain botha square and player");
		int player_num = atoi(q->fetch("player").get_chars());
		int row,column;
		if (parse_square(q->fetch("square"),row,column)) {
		    if ((player_num < 0)||(player_num > get_game()->players()))
			return new error_response(http_response::BAD_REQUEST,"Player number out of range.");
		    if (player_num == current_player()) {
			if (!square_selected)
			    return new error_response(http_response::BAD_REQUEST, "You can't dest a square until you select a source.");
			if ((column == sel_col)&&(row == sel_row)) {
			    get_game()->message("Deselected square at user request.");
			    square_selected = false;
			}
			else {
			    chess_capture_move m(sel_col,sel_row, column, row, this);
			    switch (get_game()->apply(m,player_num)) {
			    case game::move_invalid:
				get_game()->message(get_game()->get_player(player_num)->get_name()+
						    " made an invalid move.");
				break;
			    case game::game_already_done:
				get_game()->message("You can move after the game is over!");
				break;
			    default:
			    break;
			    }
			}
		    }
		    else get_game()->message(get_game()->get_player(player_num)->
					   get_name()+" selected a square out of turn.");
		    return get_game()->display(ext,player_num);
		}
		else return new error_response(http_response::BAD_REQUEST, "Couldn't find square and player number.");
	      }
	else if (first == "save")
	  {
	    map <string, string> *q = parse_query(extract_query(request));
	    if (!(q&&q->contains("file"))) return new error_response(http_response::BAD_REQUEST, "Must specify a file to save to.");
	    string filename = safeify_filename(q->fetch("file"));
	    ofstream strm;
	    strm.open(filename.get_chars());
//	    if (!(strm.good)) return new error_response(http_response::BAD_REQUEST, "File already exists.");
//	    else {
	    save(strm);
	    return new normal_response(true);
//	    }
	  }
	else			// additional request above this line
	  return new error_response(http_response::NOT_FOUND, "The requested operation was not found.");
      }
    else return new error_response(http_response::BAD_REQUEST,"Unable to decode escaped URI.");
}

bool parse_square (string square, int &row, int &col)
{
    if (square.length() !=2 )
	return false;
    switch(tolower(square[0])) {
    case 'a':
	col = 0; break;
    case 'b': col = 1; break;
    case 'c': col=2; break;
    case 'd': col = 3; break;
    case 'e': col = 4; break;
    case 'f': col = 5; break;
    case 'g': col = 6; break;
    case  'h': col = 7; break;
    default:
	return false;
    }
    if (('1' <= square[1])&&(square[1] <= '8')) {
	row =(int) (square[1]-'1');
	return true;
    }
    return false;
}

bool stomove(string s, chess_capture_move &m, board *b){
    int x1, x2, y1, y2;
    if (s.length() != 5)
	return false;

    if (!parse_square(s.substring(0,2), x1, y1))
	return false;
    if (!parse_square(s.substring(3,5), x2, y2))
	return false;

    chess_display_board *cb =(chess_display_board *)
	b->force(chess_display_board::typeinfo);
    if (!cb)
	return false;
        
    chess_capture_move copied(x1,y1,x2,y2, cb);
    m = copied;
    return true;
}
