
#include "protoRouteTable.h"
#include "protoDebug.h"

ProtoRouteTable::ProtoRouteTable()
{   
}

ProtoRouteTable::~ProtoRouteTable()
{   
    Destroy();
}

void ProtoRouteTable::Clear()
{   
    // First, get rid of entries contained in tree.
    ProtoTree::Item* next;
    while ((next = tree.GetRoot()))
    {
        ProtoTree::Item* item = tree.Remove(next);
        Entry* entry = (Entry*)item->GetValue();
        delete entry;
        delete item;
    }
    // Second, get rid of default_entry
    if (default_entry.IsValid()) default_entry.Clear();
}  // end ProtoRouteTable::Clear()

void ProtoRouteTable::Destroy()
{   
    // First, clear the tree (and default_entry)
    Clear();
    // Second, destroy the tree
    tree.Destroy();
}  // end ProtoRouteTable::Destroy()

bool ProtoRouteTable::GetRoute(const ProtoAddress&  dst, 
                               unsigned int         prefixLen,
                               ProtoAddress&        gw,
                               unsigned int&        ifIndex,
                               int&                 metric)
{
    if (0 == prefixLen) 
    {
        gw = default_entry.gateway;
        ifIndex = default_entry.iface_index;
        metric = default_entry.metric;
        return true;   
    }
    Entry* entry = GetEntry(dst, prefixLen);
    if (entry)
    {
        gw = entry->gateway;
        ifIndex = entry->iface_index;
        metric = entry->metric;
        return true;
    }
    else
    {
        return false;   
    }
}  // end ProtoRouteTable::GetRoute()

bool ProtoRouteTable::SetRoute(const ProtoAddress&  dst,
                               unsigned int         prefixLen,
                               const ProtoAddress&  gw,
                               unsigned int         ifIndex,
                               int                  metric)
{
    if (0 == prefixLen)
    {
        
        default_entry.destination = dst;
        default_entry.gateway = gw;
        default_entry.iface_index = ifIndex;
        default_entry.metric = metric;
        return true;
    }
    Entry* entry = GetEntry(dst, prefixLen);
    // (TBD) allow for multiple routes to same dst/mask ???
    if (!entry) entry = CreateEntry(dst, prefixLen);
    if (entry)
    {
        entry->gateway = gw;
        entry->iface_index = ifIndex;
        entry->metric = metric;
        return true;
    }
    else
    {
        DMSG(0, "ProtoRouteTable::SetRoute() error creating entry\n");
        return false;   
    }
}  // end ProtoRouteTable::SetRoute()

bool ProtoRouteTable::FindRoute(const ProtoAddress& dst,
                                unsigned int        prefixLen,
                                ProtoAddress&       gw,
                                unsigned int&       ifIndex,
                                int&                metric)
{
    Entry* entry = FindRouteEntry(dst, prefixLen);
    if (entry)
    {
        gw = entry->gateway;
        ifIndex = entry->iface_index;
        metric = entry->metric;
        return true;
    }
    else
    {
        return false;
    }
}  // end ProtoRouteTable::FindRoute()

bool ProtoRouteTable::DeleteRoute(const ProtoAddress& dst, 
                                  unsigned int        maskLen,
                                  const ProtoAddress* gw,
                                  unsigned int        index)
{
    Entry* entry = GetEntry(dst, maskLen);
    if (entry)
    {
        if (gw && !entry->gateway.HostIsEqual(*gw)) 
        {
            DMSG(0, "ProtoRouteTable::DeleteRoute() non-matching gateway addr\n");
            return false;
        }
        if ((0 != index) && (index != entry->iface_index))
        {
            DMSG(0, "ProtoRouteTable::DeleteRoute() non-matching interface index\n");
            return false;
        }
        DeleteEntry(entry);
        return true;
    }
    else
    {
        return false;   
    }
}  // end ProtoRouteTable::DeleteRoute()

ProtoRouteTable::Entry* ProtoRouteTable::CreateEntry(const ProtoAddress& dstAddr, 
                                                     unsigned int        prefixLen)
{
    if (!dstAddr.IsValid())
    {
        DMSG(0, "ProtoRouteTable::CreateEntry() invalid destination addr\n");
        return NULL;
    }
    Entry* entry = new Entry(dstAddr, prefixLen);
    ProtoTree::Item* item = new ProtoTree::Item();
    if (!entry || !item)
    {
        DMSG(0, "ProtoRouteTable::CreateEntry() memory allocation error: %s\n",
                GetErrorString());
        if (entry) delete entry;
        if (item) delete item;
        return NULL;   
    }
    // Bind the item and the entry
    item->Init(entry->destination.GetRawHostAddress(), prefixLen, entry);
    if (tree.Insert(item))
    {
        return entry;
    }    
    else
    {
        DMSG(0, "ProtoRouteTable::CreateEntry() entry already exists?\n");
        delete entry;
        delete item;
        return NULL;
    }
}  // end ProtoRouteTable::CreateEntry()
        
ProtoRouteTable::Entry* ProtoRouteTable::GetEntry(const ProtoAddress& dstAddr, 
                                                  unsigned int        prefixLen) const
{
    if (0 == prefixLen)
    {
        if (default_entry.IsValid()) 
            return (Entry*)&default_entry;    
        else
            return NULL;
    }    
    ProtoTree::Item* item = tree.Find(dstAddr.GetRawHostAddress(), prefixLen);
    if (item)
    {
        Entry* entry = (Entry*)item->GetValue();
        if (prefixLen == entry->prefix_len)
            return entry;
        else
            return NULL;
    }
    else
    {
        return NULL;   
    }
}  // end ProtoRouteTable::GetEntry()

ProtoRouteTable::Entry* ProtoRouteTable::FindRouteEntry(const ProtoAddress& dstAddr, 
                                                        unsigned int        prefixLen) const
{
    if (0 == prefixLen) return GetDefaultEntry();
    ProtoTree::Item* item = tree.FindPrefix(dstAddr.GetRawHostAddress(), prefixLen);
    if (item)
    {
        return (Entry*)(item->GetValue());
    }
    else
    {
        return GetDefaultEntry();
    }    
}  // end ProtoRouteTable::FindRouteEntry()

void ProtoRouteTable::DeleteEntry(ProtoRouteTable::Entry* entry)
{
    if (&default_entry == entry) 
    {
        default_entry.Clear();
        return;
    }
    ProtoTree::Item* item = tree.Find(entry->GetDestination().GetRawHostAddress(), entry->GetPrefixLength());
    if (item && (item->GetValue() == entry))
    {
        item = tree.Remove(item);
        delete item;
        delete entry;
    }
    else
    {
        DMSG(0, "ProtoRouteTable::DeleteEntry() invalid entry\n");
    }
}  // end ProtoRouteTable::DeleteEntry()



ProtoRouteTable::Entry::Entry()
    : prefix_len(0), iface_index(0), metric(-1)
{
    destination.Invalidate();
    gateway.Invalidate();
}

ProtoRouteTable::Entry::Entry(const ProtoAddress& dstAddr, unsigned int prefixLen)
 : prefix_len(prefixLen), iface_index(0)
{
    destination = dstAddr;
    gateway.Invalidate();
}

void ProtoRouteTable::Entry::Init(const ProtoAddress& dstAddr, unsigned int prefixLen)
{
    destination = dstAddr;
    prefix_len = prefixLen;
    gateway.Invalidate();
    iface_index = 0;
    metric = -1;
}  // end ProtoRouteTable::Entry::Init()

ProtoRouteTable::Iterator::Iterator(const ProtoRouteTable& theTable)
 : table(theTable), iterator(theTable.tree), default_pending(true)
{
    
}
ProtoRouteTable::Entry* ProtoRouteTable::Iterator::GetNextEntry()
{
    if (default_pending)
    {
        default_pending = false;
        Entry* entry = (Entry*)table.GetDefaultEntry();
        if (entry) return entry;
    }
    ProtoTree::Item* item = iterator.GetNextItem();
    if (item)
        return ((ProtoRouteTable::Entry*)(item->GetValue()));
    else
        return NULL; 
}  // end ProtoRouteTable::Iterator::GetNextEntry()
