/*
 * Copyright 1996-2004 by Hans Reiser, licensing governed by 
 * reiserfsprogs/README
 */

#include "fsck.h"
#include "progbar.h"

struct tree_balance * cur_tb = 0;

void reiserfsck_paste_into_item (struct path * path, const char * body, int size)
{
    struct tree_balance tb;
  
    init_tb_struct (&tb, fs, path, size);
    if (fix_nodes (/*tb.transaction_handle,*/ M_PASTE, &tb, 0/*ih*/) != CARRY_ON)
	//fix_nodes(options, tree_balance, ih_to_option, body_to_option)
	
	die ("reiserfsck_paste_into_item: fix_nodes failed");
    
    do_balance (/*tb.transaction_handle,*/ &tb, 0, body, M_PASTE, 0/*zero num*/);
}


void reiserfsck_insert_item (struct path * path, struct item_head * ih, const char * body)
{
    struct tree_balance tb;
    
    init_tb_struct (&tb, fs, path, IH_SIZE + get_ih_item_len(ih));
    if (fix_nodes (/*tb.transaction_handle,*/ M_INSERT, &tb, ih/*, body*/) != CARRY_ON)
	die ("reiserfsck_insert_item: fix_nodes failed");
    do_balance (/*tb.transaction_handle,*/ &tb, ih, body, M_INSERT, 0/*zero num*/);
}


static void free_unformatted_nodes (struct item_head * ih, struct buffer_head * bh)
{
    __u32 * punfm = (__u32 *)B_I_PITEM (bh, ih);
    unsigned int i;

    for (i = 0; i < I_UNFM_NUM (ih); i ++) {
        __u32 unfm = d32_get (punfm, i);
	if (unfm != 0) {
	    struct buffer_head * to_be_forgotten;

	    to_be_forgotten = find_buffer (fs->fs_dev, unfm, fs->fs_blocksize);
	    if (to_be_forgotten) {
		//atomic_inc(&to_be_forgotten->b_count);
		to_be_forgotten->b_count ++;
		bforget (to_be_forgotten);
	    }

	    reiserfs_free_block (fs, unfm);
	}
    }
}

void reiserfsck_delete_item (struct path * path, int temporary)
{
    struct tree_balance tb;
    struct item_head * ih = PATH_PITEM_HEAD (path);
    
    if (is_indirect_ih (ih) && !temporary)
	free_unformatted_nodes (ih, PATH_PLAST_BUFFER (path));

    init_tb_struct (&tb, fs, path, -(IH_SIZE + get_ih_item_len(ih)));

    if (fix_nodes (/*tb.transaction_handle,*/ M_DELETE, &tb, 0/*ih*/) != CARRY_ON)
	die ("reiserfsck_delete_item: fix_nodes failed");
    
    do_balance (/*tb.transaction_handle,*/ &tb, 0, 0, M_DELETE, 0/*zero num*/);
}


void reiserfsck_cut_from_item (struct path * path, int cut_size)
{
    struct tree_balance tb;
    struct item_head * ih;

    if (cut_size >= 0)
	die ("reiserfsck_cut_from_item: cut size == %d", cut_size);

    if (is_indirect_ih (ih = PATH_PITEM_HEAD (path))) {
	struct buffer_head *bh = PATH_PLAST_BUFFER (path);
	__u32 unfm_ptr = d32_get ((__u32 *)B_I_PITEM(bh, ih), I_UNFM_NUM (ih) - 1);
	if (unfm_ptr != 0) {
	    struct buffer_head * to_be_forgotten;

	    to_be_forgotten = find_buffer (fs->fs_dev, unfm_ptr, fs->fs_blocksize);
	    if (to_be_forgotten) {
		//atomic_inc(&to_be_forgotten->b_count);
		to_be_forgotten->b_count ++;
		bforget (to_be_forgotten);
	    }
	    reiserfs_free_block (fs, unfm_ptr);
	}
    }


    init_tb_struct (&tb, fs, path, cut_size);

    if (fix_nodes (/*tb.transaction_handle,*/ M_CUT, &tb, 0) != CARRY_ON)
	die ("reiserfsck_cut_from_item: fix_nodes failed");

    do_balance (/*tb.transaction_handle,*/ &tb, 0, 0, M_CUT, 0/*zero num*/);
}


/* uget_rkey is utils clone of stree.c/get_rkey */
/*
struct key * uget_rkey (struct path * path)
{
    int pos, offset = path->path_length;
    struct buffer_head * bh;

    if (offset < FIRST_PATH_ELEMENT_OFFSET)
	die ("uget_rkey: illegal offset in the path (%d)", offset);

    while (offset-- > FIRST_PATH_ELEMENT_OFFSET) {
	if (! buffer_uptodate (PATH_OFFSET_PBUFFER (path, offset)))
	    die ("uget_rkey: parent is not uptodate");

	// Parent at the path is not in the tree now.
	if (! B_IS_IN_TREE (bh = PATH_OFFSET_PBUFFER (path, offset)))
	    die ("uget_rkey: buffer on the path is not in tree");

	// Check whether position in the parrent is correct.
	if ((pos = PATH_OFFSET_POSITION (path, offset)) > B_NR_ITEMS (bh))
	    die ("uget_rkey: invalid position (%d) in the path", pos);

	// Check whether parent at the path really points to the child.
	if (get_dc_child_blocknr (B_N_CHILD (bh, pos)) != PATH_OFFSET_PBUFFER (path, offset + 1)->b_blocknr)
	    die ("uget_rkey: invalid block number (%d). Must be %ld",
		 get_dc_child_blocknr (B_N_CHILD (bh, pos)), PATH_OFFSET_PBUFFER (path, offset + 1)->b_blocknr);
	
	// Return delimiting key if position in the parent is not the last one.
	if (pos != B_NR_ITEMS (bh))
	    return B_N_PDELIM_KEY(bh, pos);
    }

    // there is no right delimiting key
    return 0;
}
*/


static unsigned long first_child (struct buffer_head * bh)
{
    return get_dc_child_blocknr (B_N_CHILD (bh, 0));
}

#if 0
static unsigned long last_child (struct buffer_head * bh)
{
    return child_block_number (bh, node_item_number (bh));
}
#endif


static unsigned long get_child (int pos, struct buffer_head * parent)
{
    if (pos == -1)
        return -1;

    if (pos > B_NR_ITEMS (parent))
        die ("get_child: no child found, should not happen: %d of %d", pos, B_NR_ITEMS (parent));
    return get_dc_child_blocknr (B_N_CHILD (parent, pos));

}


static void print (int cur, int total)
{
    if (fsck_quiet (fs))
	return;
    printf ("/%3d (of %3d)", cur, total);fflush (stdout);
}


/* erase /XXX(of XXX) */
static void erase (void)
{
    if (fsck_quiet (fs))
	return;
    printf ("\b\b\b\b\b\b\b\b\b\b\b\b\b");
    printf ("             ");
    printf ("\b\b\b\b\b\b\b\b\b\b\b\b\b");
    fflush (stdout);
}

void pass_through_tree (reiserfs_filsys_t * fs, do_after_read_t action1,
			do_on_full_path_t action2, int depth)
{
    struct buffer_head * path[MAX_HEIGHT] = {0,};
    int total[MAX_HEIGHT] = {0,};
    int cur[MAX_HEIGHT] = {0,};
    int h = 0;
    unsigned long block = get_sb_root_block (fs->fs_ondisk_sb);
    int problem;
    struct spinner spinner;

    spinner_init(&spinner, fsck_progress_file(fs));


    if (block >= get_sb_block_count (fs->fs_ondisk_sb) || not_data_block (fs, block)) {
	die ("\nBad root block %lu. (--rebuild-tree did not complete)\n", block);
    }

    while ( 1 ) {
	problem = 0;

        if (path[h])
            die ("pass_through_tree: empty slot expected");
        if (h)
            print (cur[h - 1], total[h - 1]);

	if (fs->fs_badblocks_bm && reiserfs_bitmap_test_bit (fs->fs_badblocks_bm, block)) {
	    fsck_log ("%s: block %lu specified in badblock list found in tree, whole subtree skipped\n",
		__FUNCTION__, block);
	    fsck_data (fs)->check.bad_nodes++;
	    one_more_corruption (fs, FATAL);

	    if (h == 0) {
		brelse (path[h]);
		path[h] = 0;
		break;
	    }
	    problem = 1;		
	} else {

	    spinner_touch(&spinner);
	    path[h] = bread (fs->fs_dev, block, fs->fs_blocksize);
	    if (path[h] == 0)
		/* FIXME: handle case when read failed */
		die ("pass_through_tree: unable to read %lu block on device 0x%x\n",
			block, fs->fs_dev);

	    if (action1)
		if ((problem = action1 (fs, path, h))) {
		    fsck_log (" the problem in the internal node occured (%lu), whole subtree is skipped\n", 
			path[h]->b_blocknr);
		    fsck_data (fs)->check.bad_nodes++;
 
		    if (h == 0) {
			brelse (path[h]);
			path[h] = 0;
			break;
		    }
		}
	}

	/* Time to stop. */
	if (h == depth)
	    problem ++;

        if (problem || is_leaf_node (path[h])) {
            if (!problem && action2)
                action2 (fs, path, h);

	    brelse (path[h]);
            if (h)
		erase ();

            while (h && (cur[h-1] == total[h-1] || problem)) {
                problem = 0;
                path[h] = 0;
                h --;
                brelse (path[h]);
                if (h)
                    erase ();
	    }

    	    if (h == 0) {
    	    	path[h] = 0;
		break;
    	    }

            block = get_child (cur[h - 1], path[h-1]);
            cur[h - 1] ++;
            path[h] = 0;
            continue;
	}
        total[h] = B_NR_ITEMS (path[h]) + 1;
        cur[h] = 1;
        block = first_child (path[h]);
        h ++;
    }
    spinner_clear(&spinner);
}
