/*
* $Log: $
* $Locker:  $
*/

#define GHEAP_COMPILE

#include <stdlib.h>
#include "gheap.c"
#include "sp.h"
#include <stdio.h>

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

size_t md_allocated=0;
size_t md_peek=0;
static int sp_count = -1;

void sp_init(sp_t sp,
             int w,
             int h,
             char *map)
{
   int ix;
   int s=w*h;
   sp->size = s;
   sp->width = w;
   NEWA(sp->arr,s);
   NEWA(sp->arr2,s);
   NEWA(sp->info,s);
   NEWA(sp->base,s);
   NEWA(sp->heap_node,s);
   NEWA(sp->pred,s);
   NEWA(sp->popular,s);
   sp->heap = NULL;
   for(ix = 0 ; ix < s ; ix++ )
   {
      if(! (sp->arr2[ix]=sp->arr[ix]=map[ix]) )
         sp->arr2[ix]=1;
      sp->heap_node[ix] = -1;
      sp->info[ix] = 0;
      sp->popular[ix] = 0;
      sp->base[ix] = 0;
   }
}

void sp_free(sp_t sp)
{
   free(sp->arr);
   free(sp->heap_node);
   free(sp->pred);
   if( sp->heap )
      gheap_clear(sp->heap);
#ifdef MD_STAT
   md_stat();
#endif
}

void sp_reweight(sp_t sp,
                 int  coast_penalty,
                 int  block1_penalty,
                 int  block2_penalty,
                 int  danger_penalty)
{
   int i;
   int s = sp->size;
   int w = sp->width;
   unsigned char *a = sp->arr;
   unsigned char *inf = sp->info;
   s -= w+1;
   for( i=w+1 ; i<s ; i++ )
   {
      if( ( a[i] < WATER ) && ( a[i] > 0 ) )
      {
         int c_block=0;
         int x = 3;

         if( ( a[i-1] == WATER ) &&
             ( a[i+1] < WATER ) )
         {
            x += coast_penalty;
            inf[i] |= COAST;
         }
         else if ( a[i-1] == BLOCKED )
            c_block += 1;
         
         if( ( a[i+1] == WATER ) &&
             ( a[i-1] < WATER ) )
         {
            x += coast_penalty;
            inf[i] |= COAST;
         }
         else if ( a[i+1] == BLOCKED )
            c_block += 1;
         
         if( ( a[i-w] == WATER ) &&
             ( a[i+w] < WATER ) )
         {
            x += coast_penalty;
            inf[i] |= COAST;
         }
         else if ( a[i-w] == BLOCKED )
            c_block += 1;
         
         if( ( a[i+w] == WATER ) &&
             ( a[i-w] < WATER ) )
         {
            x += coast_penalty;
            inf[i] |= COAST;
         }
         else if ( a[i+w] == BLOCKED )
            c_block += 1;
         
         if( c_block )
            if( c_block > 1 )
               x += block2_penalty;
            else
               x += block1_penalty;
         a[i] = x;
      }
      else
      {
         if( !a[i] ) inf[i] |= BASE_FL;
      }
   }
   for( i=w+1 ; i<s ; i++ )
   {
      if( ( a[i] < WATER ) &&
          ( inf[i] & COAST ) )
      {
         if( ! (inf[i-1] & COAST) )
         {
            inf[i]   |= DANGER;
            inf[i-1] |= AMBUSH;
         }

         if( ! (inf[i+1] & COAST) )
         {
            inf[i]   |= DANGER;
            inf[i+1] |= AMBUSH;
         }

         if( ! (inf[i+w] & COAST) )
         {
            inf[i]   |= DANGER;
            inf[i+w] |= AMBUSH;
         }

         if( ! (inf[i-w] & COAST) )
         {
            inf[i]   |= DANGER;
            inf[i-w] |= AMBUSH;
         }
         
         if( ( inf[i] & (DANGER|BASE_FL)) ==
             DANGER )
         {
            a[i] += danger_penalty;
         }
      }
   }
}

void sp_mark_targets(sp_t sp)
{
   int i;
   int s = sp->size;
   int w = sp->width;
   unsigned char *a = sp->arr;
   unsigned char *inf = sp->info;
   s -= w+1;
   for( i=w+1 ; i<s ; i++ )
   {
      unsigned int x = inf[i];
      if( ( (x & DROP_FL ) &&
            (!(x & DEST_FL)) ) ||
          ( x & BASE_FL) )
      {
         a[i] = 0;
      }
   }
}

void sp_reweight_dangerous_bases(sp_t sp,int weight)
{
   int i;
   int s = sp->size;
   int w = sp->width;
   char *a = sp->arr;
   char *inf = sp->info;
   s -= w+1;
   for( i=w+1 ; i<s ; i++ )
   {
      if( inf[i] & DANGER )
         a[i] = weight;
   }
}

void sp_visit(sp_t sp,int pos)
{
   sp->popular[pos]+=1;
}

int sp_robot_at(sp_t sp,int pos)
{
   sp->info[pos] & ROBOT;
}

/** Unmark as target */
void sp_unmark_target(sp_t sp,int pos)
{
   sp->arr2[pos]=EMPTY;
}

/** Mark target. */
void sp_mark_target(sp_t sp,int pos)
{
   sp->arr2[pos]=TARGET;
}

int sp_get_weight(sp_t sp,int pos)
{
   return sp->arr[pos];
}

void sp_set_weight(sp_t sp,int pos,int w)
{
   sp->arr[pos] = w;
}

/** Static only. */
static void sp_put_node(sp_t sp,int ix,int pred,int dist)
{
   int s = sp->arr[ix];
   int hn;
   /* printf("put (%d,%d)\n",ix % sp->width,ix/sp->width); */
   if( ( s < WATER ) && ( (hn=sp->heap_node[ix]) > sp_count ) )
   {
      /* printf("cp1\n"); */
      if( hn >= 0 )
      {
         int d0 = gheap_weight(sp->heap,hn);
         if( d0 > (dist +=s) )
         {
            gheap_relabel(sp->heap,hn,dist);
            sp->pred[ix] = pred;
         }
      }
      else
      {
         /* printf("cp2\n"); */
         sp->heap_node[ix] = gheap_add(sp->heap,ix,dist+s);
         sp->pred[ix]      = pred;
      }
   }
}


void sp_switch_if(sp_t sp,int use_arr2)
{
   if( use_arr2 )
   {
      unsigned char *tmp = sp->arr2;
      sp->arr2 = sp->arr;
      sp->arr = tmp;
   }
}

/* Search function:
   returns the weighted shortest distance to some of the targets.
   drom the given point (x,y).

   The narest target is written into (*fx,*fy).
   The unwighted distance is stored into in steps.
*/

int sp_shortest_path(sp_t sp,int pos,int *fpos,int *steps,
                     int use_arr2)
{
   int d=-1;
   int dummy;
   int ix;
   int w = sp->width;

   sp_switch_if(sp,use_arr2);
      
   sp_count -= 1;
   
   if( ! sp->heap ) sp->heap = gheap_new(1000);

   sp_put_node(sp,pos,-1,0);

   *steps = -1;
   
   while( (ix = gheap_get(sp->heap,&d))>=0 )
   {
      sp->heap_node[ix] = sp_count;
      if( sp->arr[ix] )
      {
         sp_put_node(sp,ix+w,ix,d);
         sp_put_node(sp,ix-w,ix,d);
         sp_put_node(sp,ix+1,ix,d);
         sp_put_node(sp,ix-1,ix,d);
      }
      else
      {
         int pred,next;
         *fpos = ix;
         *steps = 0;
         next = -1;
         while( (pred = sp->pred[ix]) >= 0 )
         {
            sp->pred[ix] = next;
            next = ix;
            ix = pred;
            *steps += 1;
         }
         sp->pred[ix] = next;
         break;
      }
   }

   while( (ix = gheap_get(sp->heap,&dummy))>=0 )
      sp->heap_node[ix] = sp_count;

   {
      int i;
#if 0
      for( i=0 ; i<sp->size ; i++)
         if( sp->heap_node[i] >= 0 )
         {
            printf("problem %d at %d (%d,%d)\n",
                   sp->heap_node[i],i,i%w,i/w);
         }
#endif
   }
#if 0
   printf(" < (%d,%d) >",x,y);
   printf("%d\n",sp->pred[x+y*w]);
   while( sp_next(sp,&x,&y) )
   {
      printf(" < (%d,%d) > ",x,y);
   }
   printf("\n");
#endif
#ifdef MD_STAT
   md_stat();
#endif
   sp_switch_if(sp,use_arr2);

   if( *steps >=0 )
      return d;
   else
      return -1;
}

typedef struct sp_array
{
   int *ptr;
   int  size;
   int  filled;
} *sp_array_t;


static sp_array_t sp_array_new()
{
   sp_array_t NEW(arr);
   arr->filled = 0;
   arr->size = 1020;
   NEWA(arr->ptr,arr->size);
   return arr;
}

static void sp_array_push(sp_array_t arr,int val)
{
   if(arr->filled >= arr->size )
   {
      arr->ptr = reall(arr->ptr,
                       (arr->size = 10+arr->filled*1.2)*sizeof(int),
                       arr->filled*sizeof(int));
   }
   arr->ptr[arr->filled++]=val;
}

static void sp_array_del(sp_array_t arr)
{
   free(arr->ptr);
   free(arr);
}

static int sp_array_pop(sp_array_t arr)
{
   if( arr->filled > 0 )
   {
      arr->filled--;;
      return arr->ptr[arr->filled];
   }
   else
   {
      return -1;
   }
}

static int sp_array_reset(sp_array_t arr)
{
   arr->filled = 0;
}

/** Static only. */
static void sp_put_node2(sp_t sp,sp_array_t array,int ix,int pred)
{
   /*   printf("<%d,%d:%d %d>",ix % sp->width,ix/sp->width,
        sp->heap_node[ix],sp->arr[ix]);*/
   if( ( sp->arr[ix] < WATER ) && ( (sp->heap_node[ix]) > sp_count ) )
   {
      sp->heap_node[ix] = sp_count;
      sp->pred[ix]      = pred;
      sp_array_push(array,ix);
   }
}

static void sp_put_node3(sp_t sp,sp_array_t array,int ix)
{
   /*   printf("<%d,%d:%d %d>",ix % sp->width,ix/sp->width,
        sp->heap_node[ix],sp->arr[ix]);*/
   if( ( sp->arr[ix] < WATER ) && ( (sp->heap_node[ix]) > sp_count ) )
   {
      sp->heap_node[ix] = sp_count;
      sp_array_push(array,ix);
   }
}

/* Search function:
   returns the weighted shortest distance to some of the targets.
   (Weights are neglected...)
   from the given point (x,y).

   The nearest target is written into (*fx,*fy).
*/
int sp_shortest_path2(sp_t sp,int pos,int *fpos,int use_arr2)
{
   int ix;
   int d = -1;
   int w = sp->width;

   sp_array_t arr1 = sp_array_new();
   sp_array_t arr2 = sp_array_new();
   sp_array_t tmp;

   sp_switch_if(sp,use_arr2);

   sp_count -= 1;

   sp_put_node2(sp,arr1,pos,-1);

   while( arr1->filled )
   {
      while( (ix = sp_array_pop(arr1)) >=0) 
      {
         sp->heap_node[ix] = sp_count;
         if( sp->arr[ix] )
         {
            sp_put_node2(sp,arr2,ix+1,ix);
            sp_put_node2(sp,arr2,ix-1,ix);
            sp_put_node2(sp,arr2,ix+w,ix);
            sp_put_node2(sp,arr2,ix-w,ix);
         }
         else
         {
            int pred,next;
            *fpos = ix;
            d = 0;
            next = -1;
            while( (pred = sp->pred[ix]) >= 0 )
            {
               sp->pred[ix] = next;
               next = ix;
               ix = pred;
               d += 1;
            }
            sp->pred[ix] = next;
            goto ready;
         }
      }
      tmp = arr1;
      arr1 = arr2;
      arr2 = tmp;
   }
 ready:
   {
      int i;
      for( i=0 ; i<arr1->filled ; i++ )
      {
         sp->heap_node[i] = sp_count;
      }
   }
   
   sp_array_del(arr1);
   sp_array_del(arr2);
#ifdef MD_STAT
   md_stat();
#endif
   sp_switch_if(sp,use_arr2);
   return d;
}

void sp_block_unreachable(sp_t sp,int pos,char *orig,char block_c)
{
   int ix;
   int w = sp->width;
   sp_array_t arr1 = sp_array_new();
   sp_array_t arr2 = sp_array_new();
   sp_array_t tmp;
   
   sp_count -= 1;
   
   sp_put_node3(sp,arr1,pos);
   
   while( arr1->filled )
   {
      while( (ix = sp_array_pop(arr1)) >=0) 
      {
         sp_put_node3(sp,arr2,ix+1);
         sp_put_node3(sp,arr2,ix-1);
         sp_put_node3(sp,arr2,ix+w);
         sp_put_node3(sp,arr2,ix-w);
      }
      tmp = arr1;
      arr1 = arr2;
      arr2 = tmp;
   }
   {
      int i;
      for( i=0 ; i<sp->size ; i++ )
      {
         if( sp->heap_node[i] > sp_count )
         {
            if( sp->arr[i] < WATER )
            {
               sp->arr[i] = BLOCKED;
               if( orig ) orig[i] = block_c;
            }
         }
      }
   }
   
   sp_array_del(arr1);
   sp_array_del(arr2);
#ifdef MD_STAT
   md_stat();
#endif
}


static void sp_put_node4(sp_t sp,sp_array_t array,int ix,int pred,int base)
{
   /*   printf("<%d,%d:%d %d>",ix % sp->width,ix/sp->width,
        sp->heap_node[ix],sp->arr[ix]);*/
   if( sp->arr[ix] < WATER )
      if( sp->heap_node[ix] > sp_count )
      {
         sp->heap_node[ix] = sp_count;
         sp_array_push(array,ix);
         sp->pred[ix] = pred;
         sp->base[ix] = base;
      }
      else if( ( sp->pred[ix] == pred ) &&
               ( (sp->base[ix] & BASE_MASK ) ==
                 (base & BASE_MASK) ) )
      {
         sp->heap_node[ix] = sp_count;
         sp_array_push(array,ix);
         sp->base[ix] |= base;
      }
}


void sp_bases_update(sp_t sp)
{
   int ix;
   int w = sp->width;
   int turn = 0;
   sp_array_t arr1 = sp_array_new();
   sp_array_t arr2 = sp_array_new();
   sp_array_t tmp;
   
   sp_count -= 1;
  
   {
      int i;
      for( i=0 ; i<sp->size ; i++ )
      {
         if( !sp->arr[i] )
            sp_put_node4(sp,arr1,i,0,(i<<BASE_SHIFT));
      }
   }
   
   while( arr1->filled )
   {
      turn += 1;
      while( (ix = sp_array_pop(arr1)) >=0) 
      {
         unsigned int base = (sp->base[ix] & BASE_MASK);
         sp_put_node4(sp,arr2,ix+1,turn,base|WEST);
         sp_put_node4(sp,arr2,ix-1,turn,base|EAST);
         sp_put_node4(sp,arr2,ix+w,turn,base|SOUTH);
         sp_put_node4(sp,arr2,ix-w,turn,base|NORTH);
      }
      tmp = arr1;
      arr1 = arr2;
      arr2 = tmp;
   }
   
   sp_array_del(arr1);
   sp_array_del(arr2);
#ifdef MD_STAT
   md_stat();
#endif
}

/** Returns nonzero if the target is reached,zero otherwise.
    Takes the last point(*x,*y) of the shortest path, and
    replaces it with the next one.
*/
int sp_next(sp_t sp,int pos)
{
   return  sp->pred[pos];
}

int sp_set_target(sp_t sp,int pos)
{
   int rc = sp->arr2[pos];
   if( rc < WATER )
      sp->arr2[pos] = 0;
   return rc;
}

void sp_cleanup_targeted(sp_t sp)
{
   int i;
   for(i=0; i<sp->size ;i++ )
      if( !sp->arr2[i] )
      {
         if( sp->arr[i] )
            sp->arr2[i] = sp->arr[i];
         else
            sp->arr2[i] = 3;
      }
}


