/* -*- coding: utf-8 -*-
 * ----------------------------------------------------------------------
 * Copyright © 2010-2013, RedJack, LLC.
 * All rights reserved.
 *
 * Please see the LICENSE.txt file in this distribution for license
 * details.
 * ----------------------------------------------------------------------
 */

#ifndef IPSET_BDD_NODES_H
#define IPSET_BDD_NODES_H


#include <stdio.h>

#include <libcork/core.h>
#include <libcork/ds.h>


/*-----------------------------------------------------------------------
 * Preliminaries
 */

/**
 * Each variable in a BDD is referred to by number.
 */
typedef unsigned int  ipset_variable;


/**
 * Each BDD terminal represents an integer value.  The integer must be
 * non-negative, but must be within the range of the <i>signed</i>
 * integer type.
 */
typedef unsigned int  ipset_value;


/**
 * An identifier for each distinct node in a BDD.
 *
 * Internal implementation note.  Since pointers are aligned to at
 * least two bytes, the ID of a terminal node has its LSB set to 1,
 * and has the terminal value stored in the remaining bits.  The ID of
 * a nonterminal node is simply a pointer to the node struct.
 */
typedef unsigned int  ipset_node_id;


/**
 * Nodes can either be terminal or nonterminal.
 */
enum ipset_node_type {
    IPSET_NONTERMINAL_NODE = 0,
    IPSET_TERMINAL_NODE = 1
};


/**
 * Return the type of node represented by a particular node ID.
 */
#define ipset_node_get_type(node_id)  ((node_id) & 0x01)

#define IPSET_NODE_ID_FORMAT  "%s%u"
#define IPSET_NODE_ID_VALUES(node_id) \
    (ipset_node_get_type((node_id)) == IPSET_NONTERMINAL_NODE? "s": ""), \
    ((node_id) >> 1)


/*-----------------------------------------------------------------------
 * Terminal nodes
 */

/**
 * Return the value of a terminal node.  The result is undefined if
 * the node ID represents a nonterminal.
 */
#define ipset_terminal_value(node_id)  ((node_id) >> 1)

/**
 * Creates a terminal node ID from a terminal value.
 */
#define ipset_terminal_node_id(value) \
    (((value) << 1) | IPSET_TERMINAL_NODE)


/*-----------------------------------------------------------------------
 * Nonterminal nodes
 */

/**
 * A nonterminal BDD node.  This is an inner node of the BDD tree.
 * The node represents one variable in an overall variable assignment.
 * The node has two children: a “low” child and a “high” child.  The
 * low child is the subtree that applies when the node's variable is
 * false or 0; the high child is the subtree that applies when it's
 * true or 1.
 *
 * This type does not take care of ensuring that all BDD nodes are
 * reduced; that is handled by the node_cache class.
 */
struct ipset_node {
    /** The reference count for this node. */
    unsigned int  refcount;
    /** The variable that this node represents. */
    ipset_variable  variable;
    /** The subtree node for when the variable is false. */
    ipset_node_id  low;
    /** The subtree node for when the variable is true. */
    ipset_node_id  high;
};

/**
 * Return the "value" of a nonterminal node.  The value of a nonterminal
 * is the index into the node array of the cache that the node belongs
 * to.
 */
#define ipset_nonterminal_value(node_id) ((node_id) >> 1)

/**
 * Creates a nonterminal node ID from a nonterminal value.
 */
#define ipset_nonterminal_node_id(value) \
    (((value) << 1) | IPSET_NONTERMINAL_NODE)

/**
 * Print out a node object.
 */
void
ipset_node_fprint(FILE *stream, struct ipset_node *node);


/*-----------------------------------------------------------------------
 * Node caches
 */

/**
 * The log2 of the size of each chunk of BDD nodes.
 */
/* 16K elements per cache */
#define IPSET_BDD_NODE_CACHE_BIT_SIZE  6
#define IPSET_BDD_NODE_CACHE_SIZE  (1 << IPSET_BDD_NODE_CACHE_BIT_SIZE)
#define IPSET_BDD_NODE_CACHE_MASK  (IPSET_BDD_NODE_CACHE_SIZE - 1)

/**
 * A cache for BDD nodes.  By creating and retrieving nodes through
 * the cache, we ensure that a BDD is reduced.
 */
struct ipset_node_cache {
    /** The storage for the nodes managed by this cache. */
    cork_array(struct ipset_node *)  chunks;
    /** The largest nonterminal index that has been handed out. */
    ipset_value  largest_index;
    /** The index of the first node in the free list. */
    ipset_value  free_list;
    /** A cache of the nonterminal nodes, keyed by their contents. */
    struct cork_hash_table  *node_cache;
};

/**
 * Returns the index of the chunk that the given nonterminal lives in.
 */
#define ipset_nonterminal_chunk_index(index) \
    ((index) >> IPSET_BDD_NODE_CACHE_BIT_SIZE)

/**
 * Returns the offset of the given nonterminal within its chunk.
 */
#define ipset_nonterminal_chunk_offset(index) \
    ((index) & IPSET_BDD_NODE_CACHE_MASK)

/**
 * Returns a pointer to the ipset_node for a given nonterminal index.
 */
#define ipset_node_cache_get_nonterminal_by_index(cache, index) \
    (&cork_array_at(&(cache)->chunks, ipset_nonterminal_chunk_index((index))) \
     [ipset_nonterminal_chunk_offset((index))])

/**
 * Returns the ipset_node for a given nonterminal node ID.
 */
#define ipset_node_cache_get_nonterminal(cache, node_id) \
    (ipset_node_cache_get_nonterminal_by_index \
     ((cache), ipset_nonterminal_value((node_id))))

/**
 * Create a new node cache.
 */
struct ipset_node_cache *
ipset_node_cache_new(void);

/**
 * Free a node cache.
 */
void
ipset_node_cache_free(struct ipset_node_cache *cache);

/**
 * Create a new nonterminal node with the given contents, returning
 * its ID.  This function ensures that there is only one node with the
 * given contents in this cache.
 *
 * Steals references to low and high.
 */
ipset_node_id
ipset_node_cache_nonterminal(struct ipset_node_cache *cache,
                             ipset_variable variable,
                             ipset_node_id low, ipset_node_id high);


/**
 * Increment the reference count of a nonterminal node.  (This is a
 * no-op for terminal nodes.)
 */
ipset_node_id
ipset_node_incref(struct ipset_node_cache *cache, ipset_node_id node);

/**
 * Decrement the reference count of a nonterminal node.  If the
 * reference count reaches 0, the storage for the node will be
 * reclaimed.  (This is a no-op for terminal nodes.)
 */
void
ipset_node_decref(struct ipset_node_cache *cache, ipset_node_id node);


/**
 * Return the number of nodes that are reachable from the given node.
 * This does not include duplicates if a node is reachable via more
 * than one path.
 */
size_t
ipset_node_reachable_count(const struct ipset_node_cache *cache,
                           ipset_node_id node);


/**
 * Return the amount of memory used by the nodes in the given BDD.
 */
size_t
ipset_node_memory_size(const struct ipset_node_cache *cache,
                       ipset_node_id node);


/**
 * Load a BDD from an input stream.  The error field is filled in with
 * an error condition is the BDD can't be read for any reason.
 */
ipset_node_id
ipset_node_cache_load(FILE *stream, struct ipset_node_cache *cache);


/**
 * Save a BDD to an output stream.  This encodes the set using only
 * those nodes that are reachable from the BDD's root node.
 */
int
ipset_node_cache_save(struct cork_stream_consumer *stream,
                      struct ipset_node_cache *cache, ipset_node_id node);


/**
 * Compare two BDD nodes, possibly from different caches, for equality.
 */
bool
ipset_node_cache_nodes_equal(const struct ipset_node_cache *cache1,
                             ipset_node_id node1,
                             const struct ipset_node_cache *cache2,
                             ipset_node_id node2);


/**
 * Save a GraphViz dot graph for a BDD.  The graph script is written
 * to the given output stream.  This graph only includes those nodes
 * that are reachable from the BDD's root node.
 */
int
ipset_node_cache_save_dot(struct cork_stream_consumer *stream,
                          struct ipset_node_cache *cache, ipset_node_id node);


/*-----------------------------------------------------------------------
 * BDD operators
 */

/**
 * A function that provides the value for each variable in a BDD.
 */
typedef bool
(*ipset_assignment_func)(const void *user_data,
                         ipset_variable variable);

/**
 * An assignment function that gets the variable values from an array
 * of gbooleans.
 */
bool
ipset_bool_array_assignment(const void *user_data,
                            ipset_variable variable);

/**
 * An assignment function that gets the variable values from an array
 * of bits.
 */
bool
ipset_bit_array_assignment(const void *user_data,
                           ipset_variable variable);

/**
 * Evaluate a BDD given a particular assignment of variables.
 */
ipset_value
ipset_node_evaluate(const struct ipset_node_cache *cache, ipset_node_id node,
                    ipset_assignment_func assignment,
                    const void *user_data);

/**
 * Add an assignment to the BDD.
 */
ipset_node_id
ipset_node_insert(struct ipset_node_cache *cache, ipset_node_id node,
                  ipset_assignment_func assignment,
                  const void *user_data, ipset_variable variable_count,
                  ipset_value value);


/*-----------------------------------------------------------------------
 * Variable assignments
 */

/**
 * Each variable in the input to a Boolean function can be true or
 * false; it can also be EITHER, which means that the variable can be
 * either true or false in a particular assignment without affecting
 * the result of the function.
 */
enum ipset_tribool {
    IPSET_FALSE = 0,
    IPSET_TRUE = 1,
    IPSET_EITHER = 2
};


/**
 * An assignment is a mapping of variable numbers to Boolean values.
 * It represents an input to a Boolean function that maps to a
 * particular output value.  Each variable in the input to a Boolean
 * function can be true or false; it can also be EITHER, which means
 * that the variable can be either true or false in a particular
 * assignment without affecting the result of the function.
 */

struct ipset_assignment {
    /**
     * The underlying variable assignments are stored in a vector of
     * tribools.  Every variable that has a true or false value must
     * appear in the vector.  Variables that are EITHER only have to
     * appear to prevent gaps in the vector.  Any variables outside
     * the range of the vector are assumed to be EITHER.
     */
    cork_array(enum ipset_tribool)  values;
};


/**
 * Create a new assignment where all variables are indeterminite.
 */
struct ipset_assignment *
ipset_assignment_new();


/**
 * Free an assignment.
 */
void
ipset_assignment_free(struct ipset_assignment *assignment);


/**
 * Compare two assignments for equality.
 */
bool
ipset_assignment_equal(const struct ipset_assignment *assignment1,
                       const struct ipset_assignment *assignment2);


/**
 * Set the given variable, and all higher variables, to the EITHER
 * value.
 */
void
ipset_assignment_cut(struct ipset_assignment *assignment, ipset_variable var);


/**
 * Clear the assignment, setting all variables to the EITHER value.
 */
void
ipset_assignment_clear(struct ipset_assignment *assignment);


/**
 * Return the value assigned to a particular variable.
 */
enum ipset_tribool
ipset_assignment_get(struct ipset_assignment *assignment, ipset_variable var);


/**
 * Set the value assigned to a particular variable.
 */
void
ipset_assignment_set(struct ipset_assignment *assignment,
                     ipset_variable var, enum ipset_tribool value);


/*-----------------------------------------------------------------------
 * Expanded assignments
 */

/**
 * An iterator for expanding a variable assignment.  For each EITHER
 * variable in the assignment, the iterator yields a result with both
 * values.
 */
struct ipset_expanded_assignment {
    /** Whether there are any more assignments in this iterator. */
    bool finished;

    /**
     * The variable values in the current expanded assignment.  Since
     * there won't be any EITHERs in the expanded assignment, we can
     * use a byte array, and represent each variable by a single bit.
     */
    struct cork_buffer  values;

    /**
     * An array containing all of the variables that are EITHER in the
     * original assignment.
     */
    cork_array(ipset_variable)  eithers;
};


/**
 * Return an iterator that expands a variable assignment.  For each
 * variable that's EITHER in the assignment, the iterator yields a
 * result with both values.  The iterator will ensure that the
 * specified number of variables are given concrete values.
 */
struct ipset_expanded_assignment *
ipset_assignment_expand(const struct ipset_assignment *assignment,
                        ipset_variable var_count);


/**
 * Free an expanded assignment iterator.
 */
void
ipset_expanded_assignment_free(struct ipset_expanded_assignment *exp);


/**
 * Advance the iterator to the next assignment.
 */
void
ipset_expanded_assignment_advance(struct ipset_expanded_assignment *exp);


/*-----------------------------------------------------------------------
 * BDD iterators
 */

/**
 * An iterator for walking through the assignments for a given BDD
 * node.
 *
 * The iterator walks through each path in the BDD tree, stopping at
 * each terminal node.  Each time we reach a terminal node, we yield a
 * new ipset_assignment object representing the assignment of variables
 * along the current path.
 *
 * We maintain a stack of nodes leading to the current terminal, which
 * allows us to backtrack up the path to find the next terminal when
 * we increment the iterator.
 */
struct ipset_bdd_iterator {
    /** Whether there are any more assignments in this iterator. */
    bool finished;

    /** The node cache that we're iterating through. */
    struct ipset_node_cache  *cache;

    /**
     * The sequence of nonterminal nodes leading to the current
     * terminal.
     */
    cork_array(ipset_node_id)  stack;

    /** The current assignment. */
    struct ipset_assignment  *assignment;

    /**
     * The value of the BDD's function when applied to the current
     * assignment.
     */
    ipset_value  value;
};


/**
 * Return an iterator that yields all of the assignments in the given
 * BDD.  The iterator contains two items of interest.  The first is an
 * ipset_assignment providing the value that each variable takes, while
 * the second is the terminal value that is the result of the BDD's
 * function when applied to that variable assignment.
 */
struct ipset_bdd_iterator *
ipset_node_iterate(struct ipset_node_cache *cache, ipset_node_id root);


/**
 * Free a BDD iterator.
 */
void
ipset_bdd_iterator_free(struct ipset_bdd_iterator *iterator);


/**
 * Advance the iterator to the next assignment.
 */
void
ipset_bdd_iterator_advance(struct ipset_bdd_iterator *iterator);


#endif  /* IPSET_BDD_NODES_H */
