/*
 * Schism Tracker - a cross-platform Impulse Tracker clone
 * copyright (c) 2003-2005 Storlek <storlek@rigelseven.com>
 * copyright (c) 2005-2008 Mrs. Brisby <mrs.brisby@nimh.org>
 * copyright (c) 2009 Storlek & Mrs. Brisby
 * copyright (c) 2010-2012 Storlek
 * URL: http://schismtracker.org/
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

#include <stdlib.h>

#include "tree.h"


typedef struct treenode {
        struct treenode *left, *right;
        void *value;
} treenode_t;

struct tree {
        treecmp_t cmp;
        treenode_t *root;
};

typedef void (*nodewalk_t) (treenode_t *node);


static void _treenode_walk(treenode_t *node, treewalk_t tapply, nodewalk_t napply)
{
        // IF IF IF IF IF

        if (!node)
                return;
        if (node->left)
                _treenode_walk(node->left, tapply, napply);
        if (node->right)
                _treenode_walk(node->right, tapply, napply);
        if (tapply)
                tapply(node->value);
        if (napply)
                napply(node);
}

static void _treenode_free(treenode_t *node)
{
        free(node);
}

tree_t *tree_alloc(treecmp_t cmp)
{
        tree_t *tree = malloc(sizeof(tree_t));
        tree->cmp = cmp;
        tree->root = NULL;
        return tree;
}

void tree_free(tree_t *tree, treewalk_t freeval)
{
        _treenode_walk(tree->root, freeval, _treenode_free);
        free(tree);
}


static treenode_t *_treenode_find(treenode_t *node, treecmp_t cmp, void *value)
{
        int r;

        while (node) {
                r = cmp(value, node->value);
                if (r == 0)
                        break;
                else if (r < 0)
                        node = node->left;
                else
                        node = node->right;
        }
        return node;
}

static treenode_t *_treenode_insert(treenode_t *node, treecmp_t cmp, treenode_t *new)
{
        int r;

        if (!node)
                return new;

        r = cmp(new->value, node->value);
        if (r < 0)
                node->left = _treenode_insert(node->left, cmp, new);
        else
                node->right = _treenode_insert(node->right, cmp, new);
        return node;
}

void tree_walk(tree_t *tree, treewalk_t apply)
{
        _treenode_walk(tree->root, apply, NULL);
}


void *tree_insert(tree_t *tree, void *value)
{
        treenode_t *node = _treenode_find(tree->root, tree->cmp, value);

        if (node)
                return node->value;

        node = malloc(sizeof(treenode_t));
        node->left = node->right = NULL;
        node->value = value;
        tree->root = _treenode_insert(tree->root, tree->cmp, node);
        return NULL;
}

void *tree_replace(tree_t *tree, void *value)
{
        void *prev;
        treenode_t *node = _treenode_find(tree->root, tree->cmp, value);

        if (node) {
                prev = node->value;
                node->value = value;
                return prev;
        }

        node = malloc(sizeof(treenode_t));
        node->left = node->right = NULL;
        node->value = value;
        tree->root = _treenode_insert(tree->root, tree->cmp, node);
        return NULL;
}

void *tree_find(tree_t *tree, void *value)
{
        treenode_t *node = _treenode_find(tree->root, tree->cmp, value);
        return node ? node->value : NULL;
}




#ifdef TEST
#include <stdio.h>
#include <string.h>

struct node {
        char *k, *v;
};

int sncmp(const void *a, const void *b)
{
        return strcmp(((struct node *) a)->k,
                      ((struct node *) b)->k);
}

struct node *snalloc(char *k, char *v)
{
        struct node *n = malloc(sizeof(struct node));
        n->k = k;
        n->v = v;
        return n;
}

int main(int argc, char **argv)
{
        // some random junk
        struct node nodes[] = {
                {"caches", "disgruntled"},
                {"logician", "daemon"},
                {"silence", "rinse"},
                {"shipwreck", "formats"},
                {"justifying", "gnash"},
                {"gadgetry", "ever"},
                {"silence", "oxidized"}, // note: duplicate key
                {"plumbing", "rickshaw"},
                {NULL, NULL},
        };
        struct node find;
        struct node *p;
        tree_t *tree;
        int n;

        // test 1: populate with tree_insert
        tree = tree_alloc(sncmp);
        for (n = 0; nodes[n].k; n++) {
                p = snalloc(nodes[n].k, nodes[n].v);
                if (tree_insert(tree, p)) {
                        printf("duplicate key %s\n", p->k);
                        free(p);
                }
        }
        find.k = "silence";
        p = tree_find(tree, &find);
        printf("%s: %s (should be 'rinse')\n", p->k, p->v);
        tree_free(tree, free);


        // test 2: populate with tree_replace
        tree = tree_alloc(sncmp);
        for (n = 0; nodes[n].k; n++) {
                p = snalloc(nodes[n].k, nodes[n].v);
                p = tree_replace(tree, p);
                if (p) {
                        printf("duplicate key %s\n", p->k);
                        free(p);
                }
        }
        find.k = "silence";
        p = tree_find(tree, &find);
        printf("%s: %s (should be 'oxidized')\n", p->k, p->v);
        tree_free(tree, free);

        return 0;
}
#endif /* TEST */

