#include "Python.h"
#include <stdio.h>
#undef   NDEBUG
#include <assert.h>

#define BoardObject_Check(v)	((v)->ob_type == &PyBoard_Type)
#define VALID_PAIR(m, x, y) (x > 0 && y > 0 && x <= m->width && y <= m->height)
#define E_NEIGH 0x0001
#define W_NEIGH 0x0002
#define N_NEIGH 0x0004
#define S_NEIGH 0x0008
#define WARN_H2O 0x0010
#define DANGER_H2O 0x0020
#define GOOD_T  0x0040
#define BAD_T   0x0080
#define PLAYER_MASK 0xFF00
#define XPLAYER_MASK 0x00FF

#define ONE_IF_SET(bits, mask) ((bits & mask) ? 1 : 0)

staticforward PyTypeObject PyBoard_Type;

typedef struct {
	PyObject_HEAD
        int width;
        int height;
        PyObject *wall;
        PyObject *plain;
        unsigned short **map; // Y Major, x = 3, y = 1 self->map[1][3]
} BoardObject;

static void
Board_dealloc(BoardObject *self)
{
  int i;
  for (i = 0; i < self->height; i++) {
    free(self->map[i]);
    self->map[i] = NULL;
  }
  free(self->map);
  self->map = NULL;
  self->wall = NULL;
  self->plain = NULL;
  PyObject_Del(self);
  return;
}

static PyObject *
Board_set_row(BoardObject *self, PyObject *args)
{
  PyObject *list;
  int row = 0;
  int list_len = 0;
  int i;
  long val;
  if (!PyArg_ParseTuple(args, "iO!:icfgBoard", &row, &PyList_Type, &list)) {
    return NULL;
  }

  list_len = PyList_GET_SIZE(list);

  assert(row < self->height - 1 && row > 0);
  assert(list_len < self->width - 1&& list_len > 0);

  for (i = 1; i < list_len + 1; i++) {
    val = PyInt_AsLong(PyList_GET_ITEM(list, i-1));
    // 1 == plain, 2 === wall, 3 == home, 4 == water
    if (val == 1 || val == 3) {
      self->map[row][i] = GOOD_T; // good terrain
    } else if (val == 2) {
      self->map[row][i] = BAD_T; // bad terrain
    } else if (val == 4) {
      self->map[row][i] = BAD_T | PLAYER_MASK; // cleared later
    } else {
      assert(0 && "Bad value");
    }
  }
  Py_INCREF((PyObject *)self);
  return (PyObject *)self; // return ourself as an lvalue
}

void print_board(BoardObject *self) {
  int i,j;

  printf("W %d H %d\n", self->width - 2, self->height -2);
  for(i = self->height - 2; i > 0; i--) {
    for (j = 1; j < self->width - 1; j++) {
      if (self->map[i][j] & GOOD_T)
	printf(".");
      else
	printf("#");
    }
    printf("\n");
  }
  
  return;
}

static int
validate_args(BoardObject *self, PyObject *args, int *w, int *h) {
  PyObject *tup;

  if (!PyArg_ParseTuple(args, "O:icfgBoard", &tup)) {
    if (!PyTuple_Check(tup) && !PyList_Check(tup))
      return 0;
  }
  if (PySequence_Fast_GET_SIZE(tup) != 2) {
    return 0;
  }
  *w = (int) PyLong_AsLong(PySequence_Fast_GET_ITEM(tup, 0));
  *h = (int) PyLong_AsLong(PySequence_Fast_GET_ITEM(tup, 1));

  if (*w < 0 || *h < 0 || *w >= (self->width) || *h >= (self->height)) {
    PyErr_SetString(PyExc_IndexError, "Width or Height is out of range");
    return 0;
  }

  return 1; //success
}

static PyObject *
Board_peek(BoardObject *self, PyObject *args) {
  int w, h;
  PyObject *ret;

  if (!validate_args(self, args, &w, &h)) {
    return NULL;
  }

  if (self->map[h][w] & GOOD_T)
    ret = self->plain;
  else
    ret = self->wall;

  Py_INCREF(ret);
  return ret;
}

static PyObject *
Board_finalize(BoardObject *self, PyObject *args) {
  int i, j;
  unsigned short **map; // alias to save some typing

  map = self->map;
  for (i = 0; i < self->height; i++) {
    for (j = 0; j < self->width; j++) {
      // locate valid neighbors
      if (i > 0) { // look south
	if (map[i-1][j] & GOOD_T)
	  map[i][j] |= S_NEIGH;

	if (map[i-1][j] & PLAYER_MASK)
	  map[i][j] |= DANGER_H2O;
      }
      if (i < self->height - 1) { // look north
	if (map[i+1][j] & GOOD_T)
	  map[i][j] |= N_NEIGH;
	if (map[i+1][j] & PLAYER_MASK)
	  map[i][j] |= DANGER_H2O;
      }
      if (j > 0) { // look west
	if (map[i][j-1] & GOOD_T)
	  map[i][j] |= W_NEIGH;
	if (map[i][j-1] & PLAYER_MASK)
	  map[i][j] |= DANGER_H2O;
      }
      if (j < self->width - 1) { // look east
	if (map[i][j+1] & GOOD_T)
	  map[i][j] |= E_NEIGH;
	if (map[i][j+1] & PLAYER_MASK)
	  map[i][j] |= DANGER_H2O;
      }
    }
  }

  // mark everything next to DANGER_H2O as WARN_H2O
  // clear the player mask while we are at it
  for (i = 0; i < self->height; i++) {
    for (j = 0; j < self->width; j++) {
      map[i][j] &= XPLAYER_MASK; // clear some bits
      if (i > 0) { // look south
	if (map[i-1][j] & DANGER_H2O)
	  map[i][j] |= WARN_H2O;
      }
      if (i < self->height - 1) { // look north
	if (map[i+1][j] & DANGER_H2O)
	  map[i][j] |= WARN_H2O;
      }
      if (j > 0) { // look west
	if (map[i][j-1] & DANGER_H2O)
	  map[i][j] |= WARN_H2O;
      }
      if (j < self->width - 1) { // look east
	if (map[i][j+1] & DANGER_H2O)
	  map[i][j] |= WARN_H2O;
      }
    }
  }

  Py_INCREF(self);
  return (PyObject *)self;
}

static PyObject *
Board_neighbors(BoardObject *self, PyObject *args) {
  PyObject *ret;
  PyObject *tmp;
  int w, h;
  unsigned short bits;
  int tuple_len = 0;
  int i = 0;

  if (!validate_args(self, args, &w, &h)) {
    return NULL;
  }

  bits = self->map[h][w];
  tuple_len = ONE_IF_SET(bits, N_NEIGH) + ONE_IF_SET(bits, S_NEIGH) +
              ONE_IF_SET(bits, E_NEIGH) + ONE_IF_SET(bits, W_NEIGH);
  // tuple len is in the range 0..4
  ret = (PyObject *)PyTuple_New(tuple_len);
  if (bits & N_NEIGH) {
    tmp = (PyObject *)PyTuple_New(2);
    PyTuple_SET_ITEM(tmp, 0, PyInt_FromLong((long)w));
    PyTuple_SET_ITEM(tmp, 1, PyInt_FromLong((long)h+1));
    PyTuple_SET_ITEM(ret, i++, tmp);
  }
  if (bits & S_NEIGH) {
    tmp = (PyObject *)PyTuple_New(2);
    PyTuple_SET_ITEM(tmp, 0, PyInt_FromLong((long)w));
    PyTuple_SET_ITEM(tmp, 1, PyInt_FromLong((long)h-1));
    PyTuple_SET_ITEM(ret, i++, tmp);
  }
  if (bits & W_NEIGH) {
    tmp = (PyObject *)PyTuple_New(2);
    PyTuple_SET_ITEM(tmp, 0, PyInt_FromLong((long)w-1));
    PyTuple_SET_ITEM(tmp, 1, PyInt_FromLong((long)h));
    PyTuple_SET_ITEM(ret, i++, tmp);
  }
  if (bits & E_NEIGH) {
    tmp = (PyObject *)PyTuple_New(2);
    PyTuple_SET_ITEM(tmp, 0, PyInt_FromLong((long)w+1));
    PyTuple_SET_ITEM(tmp, 1, PyInt_FromLong((long)h));
    PyTuple_SET_ITEM(ret, i++, tmp);
  }

  return ret;
}

static PyObject *
Board_print(BoardObject *self, PyObject *args)
{
  print_board(self);
  Py_INCREF(self);
  return (PyObject *)self;
}
static PyObject *
Board_get_danger(BoardObject *self, PyObject *args)
{
  int w, h;
  int danger;
  unsigned short foo;

  if (!validate_args(self, args, &w, &h)) {
    return NULL;
  }

  foo = self->map[h][w];
  danger = (int)((foo & PLAYER_MASK) >> 8) + (int)((foo & (DANGER_H2O | WARN_H2O)) >> 3);
  return PyLong_FromLong(danger);
}

static PyObject *
Board_mv_player(BoardObject *self, PyObject *args)
{
  print_board(self);
  Py_INCREF(self);
  return (PyObject *)self;
}
static PyObject *
Board_rm_player(BoardObject *self, PyObject *args)
{
  int w, h;
  unsigned short bits;

  if (!validate_args(self, args, &w, &h)) {
    return NULL;
  }

  bits = self->map[h][w];
  if (bits & N_NEIGH) {
    self->map[h+1][w] &= XPLAYER_MASK;
  }
  if (bits & S_NEIGH) {
    self->map[h+1][w] &= XPLAYER_MASK;
  }
  if (bits & W_NEIGH) {
    self->map[h+1][w] &= XPLAYER_MASK;
  }
  if (bits & E_NEIGH) {
    self->map[h+1][w] &= XPLAYER_MASK;
  }

  Py_INCREF(self);
  return (PyObject *)self;
}
static PyObject *
Board_add_player(BoardObject *self, PyObject *args)
{
  int w, h;
  unsigned short bits;

  if (!validate_args(self, args, &w, &h)) {
    return NULL;
  }

  bits = self->map[h][w];
  self->map[h][w] |= PLAYER_MASK & 0x0F00;
  if (bits & N_NEIGH) {
    self->map[h+1][w] |= PLAYER_MASK & 0x0800;
  }
  if (bits & S_NEIGH) {
    self->map[h+1][w] |= PLAYER_MASK & 0x0800;
  }
  if (bits & W_NEIGH) {
    self->map[h+1][w] |= PLAYER_MASK & 0x0800;
  }
  if (bits & E_NEIGH) {
    self->map[h+1][w] |= PLAYER_MASK & 0x0800;
  }

  Py_INCREF(self);
  return (PyObject *)self;
}



static PyMethodDef Board_methods[] = {
  {"peek", (PyCFunction)Board_peek, METH_VARARGS},
  {"neighbors", (PyCFunction)Board_neighbors, METH_VARARGS},
  {"set_row", (PyCFunction)Board_set_row, METH_VARARGS},
  {"finalize", (PyCFunction)Board_finalize, METH_NOARGS},
  {"print_board", (PyCFunction)Board_print, METH_NOARGS},
  {"add_player", (PyCFunction)Board_add_player, METH_VARARGS},
  {"rm_player", (PyCFunction)Board_rm_player, METH_VARARGS},
  {"move_player", (PyCFunction)Board_mv_player, METH_VARARGS},
  {"get_danger", (PyCFunction)Board_get_danger, METH_VARARGS},
  {NULL,		NULL}		/* sentinel */
};

static PyObject *
Board_getattr(BoardObject *self, char *name)
{
        return Py_FindMethod(Board_methods, (PyObject *)self, name);
}

statichere PyTypeObject PyBoard_Type = {
	/* The ob_type field must be initialized in the module init function
	 * to be portable to Windows without using C++. */
        PyObject_HEAD_INIT(&PyType_Type)
	0,			/*ob_size*/
	"icfpBoard",		/*tp_name*/
	sizeof(BoardObject),	/*tp_basicsize*/
	0,			/*tp_itemsize*/
	/* methods */
	(destructor)Board_dealloc, /*tp_dealloc*/
	0,			/*tp_print*/
	(getattrfunc)Board_getattr, /*tp_getattr*/
	0, //(setattrfunc)Permute_setattr, /*tp_setattr*/
	0,			/*tp_compare*/
	0,			/*tp_repr*/
	0,			/*tp_as_number*/
	0,	                /*tp_as_sequence*/
	0,			/*tp_as_mapping*/
	0,			/*tp_hash*/
        0,                      /*tp_call*/
        0,                      /*tp_str*/
        0,                      /*tp_getattro*/
        0,                      /*tp_setattro*/
        0,                      /*tp_as_buffer*/
        Py_TPFLAGS_DEFAULT,     /*tp_flags*/
        0,                      /*tp_doc*/
        0,                      /*tp_traverse*/
        0,                      /*tp_clear*/
        0,                      /*tp_richcompare*/
        0,                      /*tp_weaklistoffset*/
        0,                      /*tp_iter*/
        0,                      /*tp_iternext*/
        Board_methods,          /*tp_methods*/
        0,                      /*tp_members*/
        0,                      /*tp_getset*/
        0,                      /*tp_base*/
        0,                      /*tp_dict*/
        0,                      /*tp_descr_get*/
        0,                      /*tp_descr_set*/
        0,                      /*tp_dictoffset*/
        0,                      /*tp_init*/
        0,                      /*tp_alloc*/
        0,                      /*tp_new*/
        0,                      /*tp_free*/
        0,                      /*tp_is_gc*/
};


static BoardObject *
newBoardObject(int width, int height)
{
	BoardObject *self;
	const char *myplain = ".";
	int i, j;

	self = PyObject_New(BoardObject, &PyBoard_Type);
	if (self == NULL)
          return NULL;

	self->wall = NULL;
	self->plain = NULL;
	self->map = NULL;

	// create two PyStrngs, one for '#' and one for '.'
	self->wall = PyString_FromString("#");
        if (self->wall == NULL)
	  return NULL;
	self->plain = PyString_FromString(myplain);
        if (self->plain == NULL)
	  return NULL;

	self->width = width + 2;
	self->height = height + 2;

	self->map = (unsigned short **) malloc(sizeof(unsigned short *) * (self->height));
	if (self->map == NULL) {
	  return NULL;
	}
	for (i = 0; i < self->height; i++) {
	  self->map[i] = (unsigned short *) malloc(sizeof(unsigned short) * self->width);
	  if (self->map[i] == NULL) {
	    fprintf(stderr, "The pooch is screeeewed\n");
	    return NULL;
	  }
	}
	for (i = 0; i < self->height; i++) {
	  for (j = 0; j < self->width; j++) {
	    self->map[i][j] = BAD_T | PLAYER_MASK;
	  }
	}

	return self;
}

/* not static so stats_module.c can see it */
PyObject *
icfp_board(PyObject *self, PyObject *args)
{
        BoardObject *rv;
        int width = 0;
	int height = 0;
	
	if (!PyArg_ParseTuple(args, "ii", &width, &height))
		return NULL;

	assert(width > 0 && height > 0 && width < 1100 && height < 1100);
        // call object create function and return
	rv = newBoardObject(width, height);
	if ( rv == NULL ) {
          return NULL;
        }
	return (PyObject *)rv;
}