//------------------------------------------------------------------------------
/*
    Copyright (c) 2011-2013, OpenCoin, Inc.
*/
//==============================================================================

// This code is used to compare another node's transaction tree
// to our own. It returns a map containing all items that are different
// between two SHA maps. It is optimized not to descend down tree
// branches with the same branch hash. A limit can be passed so
// that we will abort early if a node sends a map to us that
// makes no sense at all. (And our sync algorithm will avoid
// synchronizing matching brances too.)

class SHAMapDeltaNode
{
public:
    SHAMapNode mNodeID;
    uint256 mOurHash, mOtherHash;

    SHAMapDeltaNode (const SHAMapNode& id, uint256 const& ourHash, uint256 const& otherHash) :
        mNodeID (id), mOurHash (ourHash), mOtherHash (otherHash)
    {
        ;
    }
};

bool SHAMap::walkBranch (SHAMapTreeNode* node, SHAMapItem::ref otherMapItem, bool isFirstMap,
                         Delta& differences, int& maxCount)
{
    // Walk a branch of a SHAMap that's matched by an empty branch or single item in the other map
    std::stack<SHAMapTreeNode*> nodeStack;
    nodeStack.push (node);

    bool emptyBranch = !otherMapItem;

    while (!nodeStack.empty ())
    {
        SHAMapTreeNode* node = nodeStack.top ();
        nodeStack.pop ();

        if (node->isInner ())
        {
            // This is an inner node, add all non-empty branches
            for (int i = 0; i < 16; ++i)
                if (!node->isEmptyBranch (i))
                    nodeStack.push (getNodePointer (node->getChildNodeID (i), node->getChildHash (i)));
        }
        else
        {
            // This is a leaf node, process its item
            SHAMapItem::pointer item = node->getItem ();

            if (!emptyBranch && (otherMapItem->getTag () < item->getTag ()))
            {
                // this item comes after the item from the other map, so add the other item
                if (isFirstMap) // this is first map, so other item is from second
                    differences.insert (std::make_pair (otherMapItem->getTag (),
                                                        std::make_pair (SHAMapItem::pointer (), otherMapItem)));
                else
                    differences.insert (std::make_pair (otherMapItem->getTag (),
                                                        std::make_pair (otherMapItem, SHAMapItem::pointer ())));

                if (--maxCount <= 0)
                    return false;

                emptyBranch = true;
            }

            if (emptyBranch || (item->getTag () != otherMapItem->getTag ()))
            {
                // unmatched
                if (isFirstMap)
                    differences.insert (std::make_pair (item->getTag (), std::make_pair (item, SHAMapItem::pointer ())));
                else
                    differences.insert (std::make_pair (item->getTag (), std::make_pair (SHAMapItem::pointer (), item)));

                if (--maxCount <= 0)
                    return false;
            }
            else
            {
                if (item->peekData () != otherMapItem->peekData ())
                {
                    // non-matching items
                    if (isFirstMap) differences.insert (std::make_pair (otherMapItem->getTag (),
                                                            std::make_pair (item, otherMapItem)));
                    else differences.insert (std::make_pair (otherMapItem->getTag (),
                                                 std::make_pair (otherMapItem, item)));

                    if (--maxCount <= 0)
                        return false;
                }

                emptyBranch = true;
            }
        }
    }

    if (!emptyBranch)
    {
        // otherMapItem was unmatched, must add
        if (isFirstMap) // this is first map, so other item is from second
            differences.insert (std::make_pair (otherMapItem->getTag (),
                                                std::make_pair (SHAMapItem::pointer (), otherMapItem)));
        else
            differences.insert (std::make_pair (otherMapItem->getTag (),
                                                std::make_pair (otherMapItem, SHAMapItem::pointer ())));

        if (--maxCount <= 0)
            return false;
    }

    return true;
}

bool SHAMap::compare (SHAMap::ref otherMap, Delta& differences, int maxCount)
{
    // compare two hash trees, add up to maxCount differences to the difference table
    // return value: true=complete table of differences given, false=too many differences
    // throws on corrupt tables or missing nodes
    // CAUTION: otherMap is not locked and must be immutable

    assert (isValid () && otherMap && otherMap->isValid ());

    std::stack<SHAMapDeltaNode> nodeStack; // track nodes we've pushed

    boost::recursive_mutex::scoped_lock sl (mLock);

    if (getHash () == otherMap->getHash ())
        return true;

    nodeStack.push (SHAMapDeltaNode (SHAMapNode (), getHash (), otherMap->getHash ()));

    while (!nodeStack.empty ())
    {
        SHAMapDeltaNode dNode (nodeStack.top ());
        nodeStack.pop ();

        SHAMapTreeNode* ourNode = getNodePointer (dNode.mNodeID, dNode.mOurHash);
        SHAMapTreeNode* otherNode = otherMap->getNodePointer (dNode.mNodeID, dNode.mOtherHash);

        if (!ourNode || !otherNode)
        {
            assert (false);
            throw SHAMapMissingNode (mType, dNode.mNodeID, uint256 ());
        }

        if (ourNode->isLeaf () && otherNode->isLeaf ())
        {
            // two leaves
            if (ourNode->getTag () == otherNode->getTag ())
            {
                if (ourNode->peekData () != otherNode->peekData ())
                {
                    differences.insert (std::make_pair (ourNode->getTag (),
                                                        std::make_pair (ourNode->getItem (), otherNode->getItem ())));

                    if (--maxCount <= 0)
                        return false;
                }
            }
            else
            {
                differences.insert (std::make_pair (ourNode->getTag (),
                                                    std::make_pair (ourNode->getItem (), SHAMapItem::pointer ())));

                if (--maxCount <= 0)
                    return false;

                differences.insert (std::make_pair (otherNode->getTag (),
                                                    std::make_pair (SHAMapItem::pointer (), otherNode->getItem ())));

                if (--maxCount <= 0)
                    return false;
            }
        }
        else if (ourNode->isInner () && otherNode->isLeaf ())
        {
            if (!walkBranch (ourNode, otherNode->getItem (), true, differences, maxCount))
                return false;
        }
        else if (ourNode->isLeaf () && otherNode->isInner ())
        {
            if (!otherMap->walkBranch (otherNode, ourNode->getItem (), false, differences, maxCount))
                return false;
        }
        else if (ourNode->isInner () && otherNode->isInner ())
        {
            for (int i = 0; i < 16; ++i)
                if (ourNode->getChildHash (i) != otherNode->getChildHash (i))
                {
                    if (otherNode->isEmptyBranch (i))
                    {
                        // We have a branch, the other tree does not
                        SHAMapTreeNode* iNode = getNodePointer (ourNode->getChildNodeID (i), ourNode->getChildHash (i));

                        if (!walkBranch (iNode, SHAMapItem::pointer (), true, differences, maxCount))
                            return false;
                    }
                    else if (ourNode->isEmptyBranch (i))
                    {
                        // The other tree has a branch, we do not
                        SHAMapTreeNode* iNode =
                            otherMap->getNodePointer (otherNode->getChildNodeID (i), otherNode->getChildHash (i));

                        if (!otherMap->walkBranch (iNode, SHAMapItem::pointer (), false, differences, maxCount))
                            return false;
                    }
                    else // The two trees have different non-empty branches
                        nodeStack.push (SHAMapDeltaNode (ourNode->getChildNodeID (i),
                                                         ourNode->getChildHash (i), otherNode->getChildHash (i)));
                }
        }
        else
            assert (false);
    }

    return true;
}

void SHAMap::walkMap (std::vector<SHAMapMissingNode>& missingNodes, int maxMissing)
{
    std::stack<SHAMapTreeNode::pointer> nodeStack;

    boost::recursive_mutex::scoped_lock sl (mLock);

    if (!root->isInner ())  // root is only node, and we have it
        return;

    nodeStack.push (root);

    while (!nodeStack.empty ())
    {
        SHAMapTreeNode::pointer node = nodeStack.top ();
        nodeStack.pop ();

        for (int i = 0; i < 16; ++i)
            if (!node->isEmptyBranch (i))
            {
                try
                {
                    SHAMapTreeNode::pointer d = getNode (node->getChildNodeID (i), node->getChildHash (i), false);

                    if (d->isInner ())
                        nodeStack.push (d);
                }
                catch (SHAMapMissingNode& n)
                {
                    missingNodes.push_back (n);

                    if (--maxMissing <= 0)
                        return;
                }
            }
    }
}
