/* Complete B-tree implementation
 *
 * Author: Remigiusz 'lRem' Modrzejewski <http://lrem.net/>
 * Distributed under the terms of GNU GPL version 2, as found on:  
 * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
 */

#include <cstdio>
#include <cerrno>
#include <cassert>
#include <queue>
#include <map>

using namespace std;

#define DATAFILE "data"
#define TREEFILE "index"
#define MISCFILE "misc"
#define NODESIZE 64
#define RECORD_SIZE 15
#define T 31
#define HELP "Possible actions: (h)elp, (a)dd, (s)earch, (d)elete, (e)dit, \
(p)rint all, (q)uit\n"

struct record/*{{{*/
{
        int numbers_count,
            numbers[RECORD_SIZE];

        void dump (void);
};
/*}}}*/
class node/*{{{*/
{
        // Internals {{{
        int n;
        bool leaf;
        int keys[NODESIZE],
            sons[NODESIZE],
            data[NODESIZE];
        // }}}
        public:
        int offset,
            father;
        //{{{Methods - this is actual B-Tree implementation
        void create(); // This is _not_ a constructor
        pair<node*, int> search (int key); // Returns the data offset
        void split(int index); // Parent is a field in node
        void merge(node &source);
        void insert(int key, int data);
        void overflow(int key);
        void remove(int key);
        void search_dump (int key);
        int dump (void);
        void debug (void);
        int validate (int bound);
        //}}}
} *tree, dummy;
/*}}}*/
class cache/*{{{*/
{
        // Internals {{{
        struct entry
        {
                node data;
                bool dirty;
        };

        FILE *index,
             *data,
             *misc;
        int next_new_data,
            next_new;
        queue<int> free_data;
        queue<int> free_nodes;
        map<int, entry> storage;
        int next_free();
        //}}}
        public:
        // Methods - everything that touches files {{{
        cache();
        ~cache();
        void flush (void);
        node& allocate (void);
        node& read (int offset, int parent);
        void write (int offset);
        void free (node &n);
        int allocate_record (void);
        record& read_record (int offset);
        void write_record (int offset, record &r);
        void free_record (int offset);
        //}}}
} disk;
/*}}}*/
int main (void)/*{{{*/
{
        char choice = 'h';
        int key, data;
        record tmp;
        
        while(choice != 'q')
        {
                switch(choice)
                {
                        case 'h':/*{{{*/
                                printf(HELP);
                                break;/*}}}*/
                        case 'a':/*{{{*/
                                printf("Key: ");
                                scanf("%d", &key);
                                printf("Numbers count: ");
                                scanf("%d", &tmp.numbers_count);
                                for(int i = 0; i < tmp.numbers_count; i++)
                                {
                                        printf("Number %d: ", i);
                                        scanf("%d", &tmp.numbers[i]);
                                }
                                data = disk.allocate_record();
                                disk.write_record(data, tmp);
                                tree->insert(key, data);
                                break;/*}}}*/
                        case 'e':/*{{{*/
                                printf("Key: ");
                                scanf("%d", &key);
                                if(tree->search(key).second == -1)
                                        break;
                                tree->remove(key);
                                scanf("%d", &key);
                                printf("Numbers count: ");
                                scanf("%d", &tmp.numbers_count);
                                for(int i = 0; i < tmp.numbers_count; i++)
                                {
                                        printf("Number %d: ", i);
                                        scanf("%d", &tmp.numbers[i]);
                                }
                                data = disk.allocate_record();
                                disk.write_record(data, tmp);
                                tree->insert(key, data);
                                break;/*}}}*/
                        case 'p':/*{{{*/
                                printf("Total records: %d\t",tree->dump());
                                break;/*}}}*/
                        case 's':/*{{{*/
                                printf("Key: ");
                                scanf("%d", &key);
                                tree->search_dump(key);
                                break;/*}}}*/
                        case 'd':/*{{{*/
                                printf("Key: ");
                                scanf("%d", &key);
                                tree->remove(key);
                                break;/*}}}*/
                        default:/*{{{*/
                                printf("Unknown choice\n");/*}}}*/
                }
#ifdef VERBOSE
                printf("VALIDATION BEGIN{{{\n");
                tree->validate(-1);
                printf("VALIDATION END}}}\n");
#else
#ifdef DEBUG
                tree->validate(-1);
#endif
#endif
                disk.flush();
                printf("> ");
                do
                        scanf("%c", &choice);
                while(isspace(choice));
        }
        return 0;
}
/*}}}*/
//{{{ Global functions
void swap (int &a, int &b)
{
        int tmp = a;
        a = b;
        b = tmp;
}
//}}}
//{{{ Methods of node
void node::create()/*{{{*/
{
        leaf = true;
        father = -1;
        n = 0;
        disk.write(offset);
}
/*}}}*/
pair<node*, int> node::search (int key)/*{{{*/
{
        int i = 0;
        while(i < n && keys[i] < key)
                i++;
        if(i <= n && keys[i] == key)
                return make_pair(this, i);
        if(leaf)
                return make_pair(this, -1);
        node &son = disk.read(sons[i], offset);
        return son.search(key);
}
/*}}}*/
void node::split(int index)/*{{{*/
{
        assert(n == 2*T-1);
        // Make the new one similar to the old one
        node &z = disk.allocate();
        z.father = father;
        z.leaf = leaf;
        z.n = T-1;
        // Move half of the keys to the new one
        for(int j = T; j--; )
        {
                z.keys[j] = keys[j+T];
                z.data[j] = data[j+T];
        }
        // If not leaf, move the subtrees
        if(!leaf)
                for(int j = T+1; j--; )
                        z.sons[j] = sons[j+T];
        n = T-1;

        // Move the last key from old son to father
        // Note that the subtree stays where it was - it will be new post-n
        node &x = disk.read(father, -1);
        for(int j = x.n; j > index; j--)
        {
                x.sons[j+1] = x.sons[j];
                x.keys[j+1] = x.keys[j];
                x.data[j+1] = x.data[j];
        }
        x.keys[index+1] = x.keys[index];
        x.data[index+1] = x.data[index];
        x.sons[index+1] = z.offset;
        x.keys[index] = keys[T-1];
        x.data[index] = data[T-1];
        x.n++;

        disk.write(offset);
        disk.write(x.offset);
        disk.write(z.offset);
}
/*}}}*/
void node::merge(node &source)/*{{{*/
{
        assert(source.n == T-1 && n == T);
        assert(offset != source.offset);
        assert(father == source.father);
        assert(leaf == source.leaf);
        //Unasserted assumption - we lack rightmost son
        for(int i = 0; i < n; i++)
        {
                keys[n+i] = source.keys[i];
                data[n+i] = source.data[i];
                sons[n+i] = source.sons[i];
        }
        n=2*T-1;
}
/*}}}*/
void node::insert(int key, int new_data)/*{{{*/
{
#ifdef DEBUG
        printf("node::insert(%d, %d)\n", key, new_data);
#endif
        pair<node*, int> x = search(key);
        if(x.second == -1)
        {
                assert(x.first->leaf);
                int i = x.first->n;
                for(; i && x.first->keys[i-1] > key; i--)
                {
                        x.first->keys[i] = x.first->keys[i-1];
                        x.first->data[i] = x.first->data[i-1];
                }
                x.first->keys[i] = key;
                x.first->data[i] = new_data;
                x.first->n++;
                disk.write(x.first->offset);
                if(x.first->n==2*T-1)
                        x.first->overflow(key);
        }
        else
        {
                disk.free_record(new_data);
        }

}
/*}}}*/
void node::overflow(int key)/*{{{*/
{
        if(this == tree)
        {
#ifdef DEBUG
                printf("node::overflow(%d) - root\n", key);
#endif
                node &s = disk.allocate();
                s.leaf = false;
                s.n = 0;
                s.father = -1;
                s.sons[0] = tree->offset;
                tree->father = s.offset;
                tree->split(0);
                disk.write(tree->offset);
                disk.write(s.offset);
                tree = &s;
        }
        else
        {
#ifdef DEBUG
                printf("node::overflow(%d) - nonroot\n", key);
#endif
                node &parent = disk.read(father, -1);
                int i = parent.n-1;
                while(i>=0 && parent.keys[i] > key)
                        i--;
                i++;
                node &y = i > 0 ? disk.read(parent.sons[i-1], -1) : dummy;
                node &z = i < parent.n ? disk.read(parent.sons[i+1], -1): dummy;
                if((y.n != dummy.n) && (y.n < 2*(T-1)))
                {
                        // Append a key to y
                        y.keys[y.n] = parent.keys[i-1];
                        y.data[y.n] = parent.data[i-1];
                        // Move the orphaned subtree where it belongs
                        y.sons[y.n] = sons[0];
                        // Move a key from this into that place
                        parent.keys[i-1] = keys[0];
                        parent.data[i-1] = data[0];
                        for(int j = 0; j < n; j++)
                        {
                                keys[j] = keys[j+1];
                                data[j] = data[j+1];
                                sons[j] = sons[j+1];
                        }
                        n--;
                        // Write changes
                        disk.write(offset);
                        disk.write(y.offset);
                        disk.write(parent.offset);
                }
                else if((z.n != dummy.n) && (z.n < 2*(T-1)))
                {
                        // Preppend a key to z
                        z.n++;
                        for(int j = z.n; j; j--)
                        {
                                z.keys[j] = z.keys[j-1];
                                z.data[j] = z.data[j-1];
                                z.sons[j] = z.sons[j-1];
                        }
                        z.keys[0] = parent.keys[i];
                        z.data[0] = parent.data[i];
                        // Move a key from this into that place
                        n--;
                        parent.keys[i] = keys[n];
                        parent.data[i] = data[n];
                        // Move the orphaned subtree where it belongs
                        z.sons[0] = sons[n+1];
                        // Write changes
                        disk.write(offset);
                        disk.write(z.offset);
                        disk.write(parent.offset);
                }
                else
                        split(i);
                if(parent.n == 2*T-1)
                        parent.overflow(key);
        }
}
/*}}}*/
#define checkroot()/*{{{*/\
{\
        if(n == 0)\
        {\
                printf("Root going down\n");\
                assert(this == tree);\
                node &son = disk.read(sons[0], offset);\
                son.father = -1;\
                tree = &son;\
                disk.free(*this);\
                disk.write(son.offset);\
                need_write = false;\
        }\
}
/*}}}*/
void node::remove(int key)/*{{{*/
{
        int index = 0;
        bool need_write = true;
        while(index < n && keys[index] < key)
                index++;
        if(leaf)
        { // 1. Leaf/*{{{*/
#ifdef DEBUG
                printf("node::remove 1 (%d)\n", key);
#endif
                // Simply delete k from x
                if(index < n && keys[index] == key)
                {
                        n--;
                        while(index < n)
                        {
                                disk.free_record(data[index]);
                                keys[index] = keys[index+1];
                                data[index] = data[index+1];
                                index++;
                        }      
                }
        }/*}}}*/
        else if(index < n && keys[index] == key)
        { // 2. Internal and k is in x/*{{{*/
                node &y = disk.read(sons[index], offset);
                node &z = disk.read(sons[index+1], offset);
                if(y.n >= T)
                { // 2a/*{{{*/
#ifdef DEBUG
                        printf("node::remove 2a\n");
#endif
                        // Swap with predecesor
                        node *current = &y;
                        while(!current->leaf)
                                current = &disk.read(current->sons[current->n],
                                                offset);
                        swap(keys[index], current->keys[current->n - 1]);
                        swap(data[index], current->data[current->n - 1]);
                        // Note this does not hurt reads count nor asymptotical
                        // computing time, as _all_ nodes just iterated will be
                        // subject to recursion.
                        
                        // Recursively delete
                        checkroot();
                        y.remove(key);
                }/*}}}*/
                else if(z.n >= T)
                { // 2b/*{{{*/
#ifdef DEBUG
                        printf("node::remove 2b\n");
#endif
                        // 2a turned around
                        node *current = &z;
                        while(!current->leaf)
                                current = &disk.read(current->sons[0], offset);
                        swap(keys[index], current->keys[0]);
                        swap(data[index], current->data[0]);

                        checkroot();
                        z.remove(key);
                }/*}}}*/
                else
                { // 2c - both sons have T-1 keys/*{{{*/
#ifdef DEBUG
                        printf("node::remove 2c\n");
#endif
                        // Append k to y
                        y.keys[y.n] = keys[index];
                        y.data[y.n] = data[index];
                        y.n++;
                        // Remove k from x
                        n--;
                        while(index <= n)
                        {
                                keys[index] = keys[index+1];
                                data[index] = data[index+1];
                                sons[index] = sons[index+1];
                                index++;
                        }
                        // Append z to y
                        y.merge(z);
                        // Get rid of z
                        disk.free(z);
                        // Recurse
                        checkroot();
                        y.remove(key);
                }/*}}}*/
        }/*}}}*/
        else
        { // 3. Internal and k is not in x/*{{{*/
                node &son = disk.read(sons[index], offset);
                if(son.n >= T)
                {
#ifdef DEBUG
                        printf("node::remove 3 void\n");
#endif
                        need_write = false;
                        checkroot();
                        son.remove(key);
                }
                else
                {
                        node &y = index > 0 ? disk.read(sons[index-1], offset)
                                : dummy;
                        node &z = index < n ? disk.read(sons[index+1], offset)
                                : dummy;
                        if(y.n >= T)
                        { // 3a - borrow from left sibling/*{{{*/
#ifdef DEBUG
                                printf("node::remove 3a\n");
#endif
                                // Preppend a key to the son
                                son.n++;
                                for(int j = son.n; j; j--)
                                {
                                        son.keys[j] = son.keys[j-1];
                                        son.data[j] = son.data[j-1];
                                        son.sons[j] = son.sons[j-1];
                                }
                                son.keys[0] = keys[index-1];
                                son.data[0] = data[index-1];
                                // Move a key from sibling into that place
                                y.n--;
                                keys[index-1] = y.keys[y.n];
                                data[index-1] = y.data[y.n];
                                // Write changes
                                disk.write(son.offset);
                                disk.write(y.offset);
                                // Move the orphaned subtree where it belongs
                                son.sons[0] = y.sons[y.n+1];
                                checkroot();
                                son.remove(key);
                        }/*}}}*/
                        else if(z.n >= T)
                        { // 3a - borrow from right sibling/*{{{*/
#ifdef DEBUG
                                printf("node::remove 3ar\n");
#endif
                                // Append a key to the son
                                son.keys[son.n] = keys[index];
                                son.data[son.n] = data[index];
                                son.n++;
                                // Move the orphaned subtree where it belongs
                                son.sons[son.n] = z.sons[0];
                                // Move a key from sibling into that place
                                keys[index] = z.keys[0];
                                data[index] = z.data[0];
                                for(int j = 0; j < z.n; j++)
                                {
                                        z.keys[j] = z.keys[j+1];
                                        z.data[j] = z.data[j+1];
                                        z.sons[j] = z.sons[j+1];
                                }
                                z.n--;
                                // Write changes
                                disk.write(son.offset);
                                disk.write(z.offset);
                                checkroot();
                                son.remove(key);
                }/*}}}*/
                        else if (index > 0)
                        { // 3b - merge with left sibling/*{{{*/
#ifdef DEBUG
                                printf("node::remove 3b\n");
#endif
                                // Append i-1 to y
                                y.keys[y.n] = keys[index-1];
                                y.data[y.n] = data[index-1];
                                y.n++;
                                // Append son to y
                                y.merge(son);
                                // Get rid of son
                                disk.write(y.offset);
                                disk.free(son);
                                // Remove i-1 from x
                                n--;
                                keys[index-1] = keys[index];
                                data[index-1] = data[index];
                                while(index <= n)
                                {
                                        keys[index] = keys[index+1];
                                        sons[index] = sons[index+1];
                                        data[index] = data[index+1];
                                        index++;
                                }
                                // Recurse
                                checkroot();
                                y.remove(key);
                        }/*}}}*/
                        else
                        { // 3b - merge with right sibling/*{{{*/
#ifdef DEBUG
                                printf("node::remove 3br\n");
#endif
                                // Append i to son
                                son.keys[son.n] = keys[index];
                                son.data[son.n] = data[index];
                                son.n++;
                                // Append z to son
                                son.merge(z);
                                // Get rid of z
                                disk.write(son.offset);
                                disk.free(z);
                                // Remove i from x
                                n--;
                                //We've removed the right son...
                                sons[index+1] = sons[index];
                                while(index <= n)
                                {
                                        keys[index] = keys[index+1];
                                        sons[index] = sons[index+1];
                                        data[index] = data[index+1];
                                        index++;
                                }
                                // Recurse
                                checkroot();
                                son.remove(key);
                        }/*}}}*/
                }
        }/*}}}*/
        if(need_write)
                disk.write(offset);
}
/*}}}*/
void node::search_dump (int key)/*{{{*/
{
        pair<node*, int> x = search(key);
        if(x.second >= 0)
        {
                record &r = disk.read_record(x.first->data[x.second]);
                r.dump();
        }
        else
        {
                printf("NOT FOUND\n");
        }
}
/*}}}*/
int node::dump (void)/*{{{*/
{
        int count = 0;
        for(int i = 0; i <= n; i++)
        {
                if(!leaf)
                {
                        node &n = disk.read(sons[i], offset);
                        count += n.dump();
                }
                printf("%d: ", keys[i]);
                record &r = disk.read_record(data[i]);
                r.dump();
        }
        if(!leaf)
        {
                node &last = disk.read(sons[n], offset);
                count += last.dump();
        }
        return count + n;
}
/*}}}*/
void node::debug (void)/*{{{*/
{
        printf("offset: %d\tfather: %d\tleaf: %s\tn: %d\n",
                        offset, father, leaf ? "true" : "false", n);
        printf("keys:\t");
        for(int i = 0; i < n; i++)
                printf("%d\t", keys[i]);
        printf("\n");
        printf("data:\t");
        for(int i = 0; i < n; i++)
                printf("%d\t", data[i]);
        printf("\n");
        if(!leaf)
        {
                printf("sons:\t");
                for(int i = 0; i < n; i++)
                        printf("%d\t", sons[i]);
                printf("%d", sons[n]);
                printf("\n");
        }
}
/*}}}*/
int node::validate (int bound)/*{{{*/
{
#ifdef VERBOSE
        debug();
#endif
        assert(n < 2*T);
        if(leaf)
        {
                for(int i = 0; i < n; i++)
                {
                        assert(bound < keys[i]);
                        bound = keys[i];
                }
        }
        else
        {
                for(int i = 0; i < n; i++)
                {
                        node &son = disk.read(sons[i], offset);
                        assert(son.offset == sons[i]);
                        assert(son.father == offset);
                        bound = son.validate(bound);
                        assert(bound < keys[i]);
                        bound = keys[i];
                }
                node &son = disk.read(sons[n], offset);
                assert(son.offset == sons[n]);
                assert(son.father == offset);
                bound = son.validate(bound);
        }
        return bound;
}
/*}}}*/
//}}}
//{{{ Methods of cache
cache::cache()/*{{{*/
{
        misc = fopen(MISCFILE, "rb+");
        if(!misc)
        {
                assert(errno == ENOENT);
                printf("Files not found, creating empty structures!\n");
                misc = fopen(MISCFILE, "wb+");
                data = fopen(DATAFILE, "wb+");
                index = fopen(TREEFILE, "wb+");
                //Tadam...
                tree = &(allocate());
                tree->create();
        }
        else
        {
                data = fopen(DATAFILE, "rb+");
                assert(data);
                index = fopen(TREEFILE, "rb+");
                assert(index);
                // Read metadata
                int root;
                fread(&root, sizeof(int), 1, misc);
                fread(&next_new, sizeof(int), 1, misc);
                fread(&next_new_data, sizeof(int), 1, misc);
                int tmp = 0;
                fread(&tmp, sizeof(int), 1, misc);
                while(tmp>=0)
                {
                        free_nodes.push(tmp);
                        fread(&tmp, sizeof(int), 1, misc);
                }
                fread(&tmp, sizeof(int), 1, misc);
                while(tmp>=0)
                {
                        free_data.push(tmp);
                        fread(&tmp, sizeof(int), 1, misc);
                }
                // Read the root
                tree = &(read(root, -1));
        }
}
/*}}}*/
cache::~cache()/*{{{*//*{{{*/
{
        // Just in case, flush the index
        flush();
        fclose(index);
        // Records are not cached for writing
        fclose(data);
        // Metadata is flushed only in here
        rewind(misc);
        fwrite(&tree->offset, sizeof(int), 1, misc);
        fwrite(&next_new, sizeof(int), 1, misc);
        fwrite(&next_new_data, sizeof(int), 1, misc);
        int tmp;
        while(!free_nodes.empty())
        {
                tmp = free_nodes.front();
                free_nodes.pop();
                fwrite(&tmp, sizeof(int), 1, misc);
        }
        tmp = -1;
        fwrite(&tmp, sizeof(int), 1, misc);
        while(!free_data.empty())
        {
                tmp = free_data.front();
                free_data.pop();
                fwrite(&tmp, sizeof(int), 1, misc);
        }
        tmp = -1;
        fwrite(&tmp, sizeof(int), 1, misc);
        fclose(misc);
}
/*}}}*/
void cache::flush (void)/*</