#define BUDDY_SYSTEM_INNER_BLOCK  0xff

#define frame_index( frame ) \
      (index_t)( (frame) - z_core.frames)

#define frame_initialize( frame ) \
    (frame)->refcount = 1;          \
    (frame)->buddy_order = 0

#define buddy_get_order( block) \
    ((frame_t*)(block))->buddy_order

#define buddy_set_order( block, order) \
     ((frame_t*)(block))->buddy_order = (order)

#define buddy_mark_busy( block ) \
    ((frame_t*)(block))->refcount = 1

#define IS_BUDDY_LEFT_BLOCK(frame)  \
  (((frame_index((frame)) >> (frame)->buddy_order) & 0x1) == 0)

#define IS_BUDDY_RIGHT_BLOCK(frame) \
    (((frame_index((frame)) >> (frame)->buddy_order) & 0x1) == 1)

#define buddy_mark_available( block ) \
    ((frame_t*)(block))->refcount = 0


static __inline link_t * buddy_bisect(link_t *block)
{
    frame_t *frame_l, *frame_r;

    frame_l = (frame_t*)block;
    frame_r = (frame_l + (1 << (frame_l->buddy_order - 1)));

    return &frame_r->buddy_link;
}

static __inline link_t *buddy_coalesce(link_t *block_1, link_t *block_2)
{
    frame_t *frame1, *frame2;

    frame1 = (frame_t*)block_1;
    frame2 = (frame_t*)block_2;

    return frame1 < frame2 ? block_1 : block_2;
}

static link_t *find_buddy(link_t *block)
{
    frame_t  *frame;
    index_t   index;
    u32_t     is_left, is_right;

    frame = (frame_t*)block;
 //   ASSERT(IS_BUDDY_ORDER_OK(frame_index_abs(zone, frame),frame->buddy_order));

    is_left = IS_BUDDY_LEFT_BLOCK( frame);
    is_right = IS_BUDDY_RIGHT_BLOCK( frame);

 //   ASSERT(is_left ^ is_right);
    if (is_left) {
        index = (frame_index(frame)) + (1 << frame->buddy_order);
    } else {  /* if (is_right) */
        index = (frame_index(frame)) - (1 << frame->buddy_order);
    }

    if ( index < z_core.count)
    {
        if (z_core.frames[index].buddy_order == frame->buddy_order &&
            z_core.frames[index].refcount == 0) {
            return &z_core.frames[index].buddy_link;
      }
    }

    return NULL;
}


static link_t *buddy_find_block(link_t *child, u32_t order)
{
    frame_t *frame;
    index_t index;

    frame = (frame_t*)child;

    index = frame_index(frame);
    do {
        if (z_core.frames[index].buddy_order != order)
            return &z_core.frames[index].buddy_link;

    } while(index-- > 0);
    return NULL;
}

static void buddy_system_free(link_t *block)
{
    link_t *buddy, *hlp;
    u32_t i;

    /*
	 * Determine block's order.
	 */
    i = buddy_get_order(block);

  //  ASSERT(i <= z->max_order);

    if (i != z_core.max_order)
    {
		/*
		 * See if there is any buddy in the list of order i.
		 */
        buddy = find_buddy( block );
        if (buddy)
		{

        //    ASSERT(buddy_get_order(z, buddy) == i);
			/*
			 * Remove buddy from the list of order i.
			 */
			list_remove(buddy);

			/*
			 * Invalidate order of both block and buddy.
			 */
            buddy_set_order(block, BUDDY_SYSTEM_INNER_BLOCK);
            buddy_set_order(buddy, BUDDY_SYSTEM_INNER_BLOCK);

			/*
			 * Coalesce block and buddy into one block.
			 */
            hlp = buddy_coalesce( block, buddy );

			/*
			 * Set order of the coalesced block to i + 1.
			 */
            buddy_set_order(hlp, i + 1);

			/*
			 * Recursively add the coalesced block to the list of order i + 1.
			 */
            buddy_system_free( hlp );
			return;
		}
	}
	/*
	 * Insert block into the list of order i.
	 */
    list_append(block, &z_core.order[i]);
}


static link_t* buddy_alloc( u32_t i)
{
	link_t *res, *hlp;

    ASSERT(i <= z_core.max_order);

	/*
	 * If the list of order i is not empty,
	 * the request can be immediatelly satisfied.
	 */
    if (!list_empty(&z_core.order[i])) {
        res = z_core.order[i].next;
		list_remove(res);
		buddy_mark_busy(res);
		return res;
	}
	/*
	 * If order i is already the maximal order,
	 * the request cannot be satisfied.
	 */
    if (i == z_core.max_order)
		return NULL;

	/*
	 * Try to recursively satisfy the request from higher order lists.
	 */
    hlp = buddy_alloc( i + 1 );

	/*
	 * The request could not be satisfied
	 * from higher order lists.
	 */
	if (!hlp)
		return NULL;

	res = hlp;

	/*
	 * Bisect the block and set order of both of its parts to i.
	 */
	hlp = buddy_bisect( res );

	buddy_set_order(res, i);
	buddy_set_order(hlp, i);

	/*
	 * Return the other half to buddy system. Mark the first part
	 * full, so that it won't coalesce again.
	 */
	buddy_mark_busy(res);
	buddy_system_free( hlp );

	return res;
}

static link_t* buddy_alloc_block(link_t *block)
{
	link_t *left,*right, *tmp;
    u32_t order;

    left = buddy_find_block(block, BUDDY_SYSTEM_INNER_BLOCK);
    ASSERT(left);
	list_remove(left);
    while (1)
   {
        if ( !buddy_get_order(left))
        {
            buddy_mark_busy(left);
			return left;
		}

        order = buddy_get_order(left);

        right = buddy_bisect(left);
        buddy_set_order(left, order-1);
        buddy_set_order(right, order-1);

        tmp = buddy_find_block( block, BUDDY_SYSTEM_INNER_BLOCK);

        if (tmp == right) {
            right = left;
            left = tmp;
        }
        ASSERT(tmp == left);
        buddy_mark_busy(left);
        buddy_system_free(right);
        buddy_mark_available(left);
	}
}

static void zone_create(zone_t *z, pfn_t start, count_t count)
{
    unsigned int i;

    spinlock_initialize(&z->lock);

    z->base = start;
    z->count = count;
    z->free_count = count;
    z->busy_count = 0;

    z->max_order = fnzb(count);

    ASSERT(z->max_order < BUDDY_SYSTEM_INNER_BLOCK);

    for (i = 0; i <= z->max_order; i++)
        list_initialize(&z->order[i]);

    z->frames = (frame_t *)balloc(count*sizeof(frame_t));

    for (i = 0; i < count; i++)
		frame_initialize(&z->frames[i]);

/*
    for (i = 0; i < count; i++) {
        z_core.frames[i].buddy_order=0;
        z_core.frames[i].parent = NULL;
        z_core.frames[i].refcount=1;
	}

    for (i = 0; i < count; i++)
    {
        z_core.frames[i].refcount = 0;
        buddy_system_free(&z_core.frames[i].buddy_link);
    }
*/

    DBG("create zone: base %x count %x order %d\n",
         start, count, z->max_order);

}

static void zone_mark_unavailable(zone_t *zone, index_t frame_idx)
{
	frame_t *frame;
	link_t *link;

    ASSERT(frame_idx < zone->count);

    frame = &zone->frames[frame_idx];

	if (frame->refcount)
		return;
    link = buddy_alloc_block( &frame->buddy_link);
    ASSERT(link);
	zone->free_count--;
}

static void zone_reserve(zone_t *z, pfn_t base, count_t count)
{
    int i;
    pfn_t top = base + count;

    if( (base+count < z->base)||(base > z->base+z->count))
        return;

    if(base < z->base)
        base = z->base;

    if(top > z->base+z->count)
        top = z->base+z->count;

    DBG("zone reserve base %x top %x\n", base, top);

    for (i = base; i < top; i++)
        zone_mark_unavailable(z, i - z->base);

};

static void zone_release(zone_t *z, pfn_t base, count_t count)
{
    int i;
    pfn_t top = base+count;

    if( (base+count < z->base)||(base > z->base+z->count))
        return;

    if(base < z->base)
        base = z->base;

    if(top > z->base+z->count)
        top = z->base+z->count;

    DBG("zone release base %x top %x\n", base, top);

    for (i = base; i < top; i++) {
        z->frames[i-z->base].refcount = 0;
        buddy_system_free(&z->frames[i-z->base].buddy_link);
    }
};

static inline frame_t * zone_get_frame(zone_t *zone, index_t frame_idx)
{
    ASSERT(frame_idx < zone->count);
	return &zone->frames[frame_idx];
}

void __fastcall frame_set_parent(pfn_t pfn, void *data)
{
  spinlock_lock(&z_core.lock);
  zone_get_frame(&z_core, pfn-z_core.base)->parent = data;
  spinlock_unlock(&z_core.lock);
}

void* __fastcall frame_get_parent(pfn_t pfn)
{
	void *res;

    spinlock_lock(&z_core.lock);
    res = zone_get_frame(&z_core, pfn)->parent;
    spinlock_unlock(&z_core.lock);

	return res;
}