#include "ruby.h"
#include "sp.h"

#define NEWA(V,S) V=malloc((S)*sizeof(*V))
#define NEW(V)    V=malloc(sizeof(*(V)))

static VALUE rb_cSP;

static VALUE
rb_SP__new(VALUE class,
           VALUE width,
           VALUE height,
           VALUE map)
{
   int w = NUM2INT(width);
   int h = NUM2INT(height);
   VALUE obj;
   sp_t sp;
   obj = Data_Make_Struct(rb_cSP,sp_s,NULL,sp_free,sp);
   sp_init(sp,w,h,RSTRING(map)->ptr);
   return obj;
}

static VALUE
rb_SP_unmark_target(VALUE self,VALUE pos)
{
   sp_t sp;
   int p;
   Data_Get_Struct(self,sp_s,sp);
   p = NUM2INT(pos);
   if( !sp->arr[p] )
      sp->arr[p] = 3;
   if( !sp->arr2[p] )
      sp->arr2[p] = 3;
}

static VALUE
rb_SP_mark_targets(VALUE self)
{
   sp_t sp;
   Data_Get_Struct(self,sp_s,sp);
   sp_mark_targets(sp);
}


static VALUE
rb_SP_set_weight(VALUE self,VALUE pos,VALUE val)
{
   sp_t sp;
   Data_Get_Struct(self,sp_s,sp);
   sp_set_weight(sp,NUM2INT(pos),NUM2INT(val));
   return val;
}

static VALUE
rb_SP_get_weight(VALUE self,VALUE pos)
{
   sp_t sp;
   Data_Get_Struct(self,sp_s,sp);
   return INT2FIX((int)sp_get_weight(sp,NUM2INT(pos)));
}

static VALUE
rb_SP_search(VALUE self,VALUE pos)
{
   sp_t sp;
   int dist,steps,fpos;
   fpos = -1;
   Data_Get_Struct(self,sp_s,sp);
   dist = sp_shortest_path(sp,NUM2INT(pos),&fpos,&steps,0);
   return rb_ary_new3(4,INT2FIX(dist),INT2FIX(steps),INT2FIX(fpos));
}

static VALUE
rb_SP_search2(VALUE self,VALUE pos)
{
   sp_t sp;
   int dist,steps,fpos;
   fpos = -1;
   Data_Get_Struct(self,sp_s,sp);
   dist = sp_shortest_path2(sp,NUM2INT(pos),&fpos,0);
   return rb_ary_new3(3,INT2FIX(dist),INT2FIX(dist),INT2FIX(fpos));
}

static VALUE
rb_SP_search3(VALUE self,VALUE pos,VALUE targets)
{
   sp_t sp;
   int i,dist,steps,fpos;
   VALUE *ptr = RARRAY(targets)->ptr;
   long num_targets = RARRAY(targets)->len;
   int *weights;
   int count = 0;
   int *x = alloca(num_targets*sizeof(*x));
   Data_Get_Struct(self,sp_s,sp);
   fpos =  -1;
   
   for( i=0 ; i<num_targets ; i++ )
      count += ((*(x+i)=sp_set_target(sp,NUM2INT(ptr[i])))<WATER)?1:0;
   
   if( count )
   {
      dist = sp_shortest_path2(sp,NUM2INT(pos),&fpos,1);
      for( i=0 ; i<num_targets ; i++ )
         sp_set_target(sp,NUM2INT(ptr[i]));
   }
   
   return rb_ary_new3(3,INT2FIX(dist),INT2FIX(dist),INT2FIX(fpos));
}

static VALUE
rb_SP_search4(VALUE self,VALUE pos,VALUE targets)
{
   sp_t sp;
   int i,dist,steps,fpos;
   VALUE *ptr = RARRAY(targets)->ptr;
   long num_targets = RARRAY(targets)->len;
   int *weights;
   int count = 0;
   int *x = alloca(num_targets*sizeof(*x));
   Data_Get_Struct(self,sp_s,sp);
   fpos =  -1;
   
   for( i=0 ; i<num_targets ; i++ )
      count += ((*(x+i)=sp_set_target(sp,NUM2INT(ptr[i])))<WATER)?1:0;
   
   if( count )
   {
      dist = sp_shortest_path(sp,NUM2INT(pos),&fpos,&steps,1);
      for( i=0 ; i<num_targets ; i++ )
         sp_set_target(sp,NUM2INT(ptr[i]));
   }
   return rb_ary_new3(3,INT2FIX(dist),INT2FIX(steps),INT2FIX(fpos));
}

static VALUE
rb_SP_block_unreachable(VALUE self,VALUE pos,VALUE orig)
{
   sp_t sp;
   Data_Get_Struct(self,sp_s,sp);
   sp_block_unreachable(sp,NUM2INT(pos),RSTRING(orig)->ptr,'#');
   return self;
}

static VALUE
rb_SP_base_update(VALUE self,VALUE pos)
{
   sp_t sp;
   Data_Get_Struct(self,sp_s,sp);
   sp_bases_update(sp);
   return self;
}

static VALUE
rb_SP_reweight(VALUE self,VALUE p1,VALUE p2,VALUE p3,VALUE p4)
{
   sp_t sp;
   Data_Get_Struct(self,sp_s,sp);
   sp_reweight(sp,NUM2INT(p1),NUM2INT(p2),NUM2INT(p3),NUM2INT(p4));
   return self;
}


static VALUE
rb_SP_next(VALUE self,VALUE pos)
{
   sp_t sp;
   Data_Get_Struct(self,sp_s,sp);

   return INT2FIX(sp_next(sp,NUM2INT(pos)));
}

static VALUE
rb_SP_base_info(VALUE self,VALUE pos)
{
   sp_t sp;
   Data_Get_Struct(self,sp_s,sp);
   return INT2FIX(sp->base[NUM2INT(pos)]>>BASE_SHIFT);
}

static VALUE
rb_SP_base_dir(VALUE self,VALUE pos)
{
   sp_t sp;
   Data_Get_Struct(self,sp_s,sp);
   return INT2FIX(sp->base[NUM2INT(pos)]&DIR_MASK);
}

static VALUE
rb_SP_base_check(VALUE self,VALUE pos,VALUE base,VALUE dir)
{
   sp_t sp;
   int x;
   int p = NUM2INT(pos);
   Data_Get_Struct(self,sp_s,sp);
   if( (p >= 0) && (p < sp->size) )
   {
      x = sp->base[p];
      if( ( x == NUM2INT(base) ) &&
          ( x & NUM2INT(dir) ) )
         return Qtrue;
   }
   return Qfalse;
}

static VALUE
rb_SP_get_info(VALUE self,VALUE pos)
{
   sp_t sp;
   int p = NUM2INT(pos);
   Data_Get_Struct(self,sp_s,sp);
   if( (p >= 0) && (p < sp->size) )
   {
      return INT2FIX(sp->info[pos]);
   }
   return INT2FIX(0);
}

static VALUE
rb_SP_set_info(VALUE self,VALUE pos,VALUE info)
{
   sp_t sp;
   int p = NUM2INT(pos);
   Data_Get_Struct(self,sp_s,sp);
   if( (p >= 0) && (p < sp->size) )
   {
      sp->info[p] |= (unsigned char)(NUM2INT(info));
      return INT2FIX(sp->info[p]);
   }
   return Qnil;
}

static VALUE
rb_SP_check_info(VALUE self,VALUE pos,VALUE info)
{
   sp_t sp;
   int p = NUM2INT(pos);
   Data_Get_Struct(self,sp_s,sp);
   if( (p >= 0) && (p < sp->size) )
   {
      if(sp->info[p] & (unsigned)(NUM2INT(info)))
         return pos;
   }
   return Qfalse;
}

static VALUE
rb_SP_unset_info(VALUE self,VALUE pos,VALUE info)
{
   sp_t sp;
   int p = NUM2INT(pos);
   Data_Get_Struct(self,sp_s,sp);
   if( (p >= 0) && (p < sp->size) )
   {
      sp->info[p] &= (unsigned char)(255-(NUM2INT(info)&255));
      return INT2FIX(sp->info[p]);
   }
   return Qnil;
}

static VALUE
rb_SP_cleanup_targeted(VALUE self)
{
   sp_t sp;
   Data_Get_Struct(self,sp_s,sp);
   sp_cleanup_targeted(sp);
   return Qnil;
}

void Init_sp_ext()
{
   rb_cSP = rb_define_class("PathSearch",rb_cObject);
   
   rb_define_singleton_method(rb_cSP,"new",rb_SP__new,3);
   
   rb_define_method(rb_cSP,"unmark",rb_SP_unmark_target,1);
   rb_define_method(rb_cSP,"mark_targets",rb_SP_mark_targets,0);
   rb_define_method(rb_cSP,"[]=",rb_SP_set_weight,2);
   rb_define_method(rb_cSP,"[]",rb_SP_get_weight,1);
   rb_define_method(rb_cSP,"weighted_search",rb_SP_search,1);
   rb_define_method(rb_cSP,"unweighted_search",rb_SP_search2,1);
   rb_define_method(rb_cSP,"targeted_search",rb_SP_search3,2);
   rb_define_method(rb_cSP,"targeted_weighted_search",rb_SP_search4,2);
   rb_define_method(rb_cSP,"block_unreachable",rb_SP_block_unreachable,2);
   rb_define_method(rb_cSP,"base_update",rb_SP_base_update,0);
   rb_define_method(rb_cSP,"reweight",rb_SP_reweight,4);
   rb_define_method(rb_cSP,"next",rb_SP_next,1);
   rb_define_method(rb_cSP,"base",rb_SP_base_info,1);
   rb_define_method(rb_cSP,"base_dir",rb_SP_base_dir,1);
   rb_define_method(rb_cSP,"base_check",rb_SP_base_check,1);
   rb_define_method(rb_cSP,"info",rb_SP_get_info,1);
   rb_define_method(rb_cSP,"set_info",rb_SP_set_info,2);
   rb_define_method(rb_cSP,"unset_info",rb_SP_unset_info,2);
   rb_define_method(rb_cSP,"check_info",rb_SP_check_info,2);
   rb_define_method(rb_cSP,"cleanup_targeted",rb_SP_cleanup_targeted,0);
}
