/****************************************************************************
 *
 *  MODULE:        r.terraflow
 *
 *  COPYRIGHT (C) 2007 Laura Toma
 *
 *  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.
 *
 *****************************************************************************/

#include "ccforest.h"
#include "sortutils.h"
#include "streamutils.h"

#if (0)
/* ---------------------------------------------------------------------- */
static int valueCmp(const keyvalue<long> &a, const keyvalue<long> &b)
{
    if (a.dst() < b.dst())
        return -1;
    if (a.dst() > b.dst())
        return 1;

    if (a.src() < b.src())
        return -1;
    if (a.src() > b.src())
        return 1;

    return 0;
}

/* ---------------------------------------------------------------------- */

static int valueCmp(const keyvalue<int> &a, const keyvalue<int> &b)
{
    if (a.dst() < b.dst())
        return -1;
    if (a.dst() > b.dst())
        return 1;

    if (a.src() < b.src())
        return -1;
    if (a.src() > b.src())
        return 1;

    return 0;
}
#endif

/* ---------------------------------------------------------------------- */

template <class T>
ccforest<T>::ccforest()
{
    edgeStream = new AMI_STREAM<ccedge>();
    rootStream = new AMI_STREAM<cckeyvalue>();
    superTree = NULL;
    rootCycles = 0;
    foundAllRoots = 0;
    savedRootValid = 0;
}

/* ---------------------------------------------------------------------- */

template <class T>
ccforest<T>::~ccforest()
{
    delete edgeStream;
    delete rootStream;
    if (superTree)
        delete superTree;
}

/* ---------------------------------------------------------------------- */

template <class T>
off_t ccforest<T>::size()
{
    off_t streamLength = edgeStream->stream_len();
    return streamLength;
}

/* ---------------------------------------------------------------------- */

template <class T>
void ccforest<T>::removeDuplicates(T src, T parent,
                                   EMPQueueAdaptive<cckeyvalue, T> &pq)
{
    cckeyvalue kv;

    while (pq.min(kv) && (src == kv.getPriority())) {
        pq.extract_min(kv);
        if (kv.getValue() != parent) { /* cycles... */
            rootCycles++;
            if (parent < kv.getValue()) {
                superTree->insert(parent, kv.getValue());
            }
            else {
                superTree->insert(kv.getValue(), parent);
            }
            DEBUG_CCFOREST cerr << "ROOT CYCLE DETECTED! " << src << " ("
                                << parent << "," << kv.getValue() << ")"
                                << endl;
        }
    }
}

/* ---------------------------------------------------------------------- */

/* needs to be re-entrant */
template <class T>
void ccforest<T>::findAllRoots(int depth)
{
    if (foundAllRoots)
        return;
    foundAllRoots = 1;
    Rtimer rt;
    rt_start(rt);

    if (depth > 5) {
        cerr << "WARNING: excessive recursion in ccforest (ignored)" << endl;
    }

    int explicitRootCount = 0;
    assert(!superTree);
    superTree = new ccforest<T>();

    if (stats)
        DEBUG_CCFOREST *stats << "sort edgeStream (by cclabel)): ";
    keyCmpKeyvalueType<T> fo;
    sort(&edgeStream, fo); /* XXX -changed this to use a cmp obj  */

    /* time forward processing */
    EMPQueueAdaptive<cckeyvalue, T> *pq =
        new EMPQueueAdaptive<cckeyvalue, T>(); /* parent queue */

    off_t streamLength = edgeStream->stream_len();
    T prevSrc = T(-1);
    T parent = T(-1);
    ccedge prevEdge;
    for (off_t i = 0; i < streamLength; i++) {
        ccedge *e;
        AMI_err ae = edgeStream->read_item(&e);
        assert(ae == AMI_ERROR_NO_ERROR);

#if (0)
        if (stats) {
            DEBUG_CCFOREST *stats << "------------------------------" << endl;
            DEBUG_CCFOREST *stats << "processing edge " << *e << endl;
        }
        DEBUG_CCFOREST pq->print();
#endif

        if (*e == prevEdge) {
            if (stats)
                DEBUG_CCFOREST *stats << "\tduplicate " << *e << " removed\n";
            continue; /* already have this done */
        }
        prevEdge = *e;

        if (stats)
            DEBUG_CCFOREST *stats << "processing edge " << *e << endl;

        /* find root (assign parent) */
        if (e->src() != prevSrc) {
            prevSrc = e->src();
            cckeyvalue kv;
            /* check if we have a key we don't use. */
            while (pq->min(kv) && (kv.getPriority() < e->src())) {
                pq->extract_min(kv);
                assert(kv.src() >= kv.dst());
                removeDuplicates(kv.src(), kv.dst(), *pq);
                ae = rootStream->write_item(kv); /* save root */
                assert(ae == AMI_ERROR_NO_ERROR);
            }
            /* try to find our root */
            if (pq->min(kv) && ((e->src() == kv.getPriority()))) {
                pq->extract_min(kv);
                parent = kv.getValue();
                removeDuplicates(e->src(), parent, *pq);
            }
            else {
                parent = e->src(); /* we are root */
                explicitRootCount++;
                /* technically, we could skip this part. the lookup function
           automatically assumes that values without parents are roots */
            }

            /* save result */
            cckeyvalue kroot(e->src(), parent);
            assert(kroot.src() >= kroot.dst());
            ae = rootStream->write_item(kroot);
            assert(ae == AMI_ERROR_NO_ERROR);
        }
#ifndef NDEBUG
        cckeyvalue kv2;
        assert(pq->is_empty() ||
               (pq->min(kv2) && kv2.getPriority() > e->src()));
#endif

        /* insert */
        cckeyvalue kv(e->dst(), parent);
        assert(kv.src() >= kv.dst());
        pq->insert(kv);

        /* cout << "identified: " << kroot << endl; */
    }

    /* drain the priority queue */
    if (stats)
        DEBUG_CCFOREST *stats << "draining priority queue" << endl;
    while (!pq->is_empty()) {
        cckeyvalue kv;
        pq->extract_min(kv);
        assert(kv.src() >= kv.dst());
        if (stats)
            DEBUG_CCFOREST *stats << "processing edge " << kv << endl;

        removeDuplicates(kv.src(), kv.dst(), *pq);
        AMI_err ae = rootStream->write_item(kv);
        assert(ae == AMI_ERROR_NO_ERROR);
    }
    delete pq;

    /* note that rootStream is naturally ordered by src */

    if (superTree->size()) {
        if (stats) {
            DEBUG_CCFOREST *stats << "resolving cycles..." << endl;
            /* printStream(rootStream); */
            DEBUG_CCFOREST *stats << "sort rootStream: ";
        }

        AMI_STREAM<cckeyvalue> *sortedRootStream;
        dstCmpKeyvalueType<T> dstfo;
        sortedRootStream = sort(rootStream, dstfo);
        /* XXX replaced this to use a cmp object -- laura
           AMI_STREAM<cckeyvalue>*sortedRootStream=new AMI_STREAM<cckeyvalue>();
           AMI_err ae = AMI_sort(rootStream, sortedRootStream, valueCmp);
           assert(ae == AMI_ERROR_NO_ERROR);
        */
        delete rootStream;

        cckeyvalue *kv;
        T parent;
        AMI_err ae;

        AMI_STREAM<cckeyvalue> *relabeledRootStream =
            new AMI_STREAM<cckeyvalue>();
        ae = sortedRootStream->seek(0);
        superTree->findAllRoots(depth + 1);
        while ((ae = sortedRootStream->read_item(&kv)) == AMI_ERROR_NO_ERROR) {
            parent = superTree->findNextRoot(kv->dst());
            ae = relabeledRootStream->write_item(cckeyvalue(kv->src(), parent));
            assert(ae == AMI_ERROR_NO_ERROR);
        }
        delete sortedRootStream;

        if (stats)
            DEBUG_CCFOREST *stats << "sort relabeledRootStream: ";
        rootStream = sort(relabeledRootStream, fo);
        /* laura: changed  this
           rootStream = new AMI_STREAM<cckeyvalue>();
           ae = AMI_sort(relabeledRootStream, rootStream);
           assert(ae == AMI_ERROR_NO_ERROR);
        */
        delete relabeledRootStream;

        if (stats)
            DEBUG_CCFOREST *stats << "resolving cycles... done." << endl;
    }
    rootStream->seek(0);

    if (stats) {
        DEBUG_CCFOREST *stats
            << "Rootstream length=" << rootStream->stream_len() << endl;
        DEBUG_CCFOREST printStream(*stats, rootStream);
        DEBUG_CCFOREST *stats << "Explicit root count=" << explicitRootCount
                              << endl;
    }

    rt_stop(rt);
    if (stats)
        stats->recordTime("ccforest::findAllRoots", (long int)rt_seconds(rt));
}

/* ---------------------------------------------------------------------- */

template <class T>
void ccforest<T>::insert(const T &i, const T &j)
{
    ccedge e(i, j);
    /* assert(i<j);  not required, as long as it's consistent. */
    assert(i != j); /* meaningless */
    AMI_err ae = edgeStream->write_item(e);
    assert(ae == AMI_ERROR_NO_ERROR);
    /* cout << "INST " << i << ", " << j << endl; */
}

/* ---------------------------------------------------------------------- */

template <class T>
T ccforest<T>::findNextRoot(const T &i)
{
    AMI_err ae;
    cckeyvalue *kroot;
    T retRoot;

    findAllRoots(); /* find all the roots if not done */

    if (stats)
        DEBUG_CCFOREST *stats << "looking for " << i << endl;
    if (!savedRootValid || savedRoot.src() < i) { /* need to read more */
        ae = rootStream->read_item(&kroot);
        while (ae == AMI_ERROR_NO_ERROR && kroot->src() < i) {
            ae = rootStream->read_item(&kroot);
        }
        if (ae == AMI_ERROR_NO_ERROR) {
            savedRoot = *kroot;
            savedRootValid = 1;
        }
        else {
            savedRootValid = -1; /* to avoid reading uselessly */
        }
    }

    if (savedRootValid == 1 && savedRoot.src() == i) { /* check savedRoot */
        retRoot = savedRoot.dst();
        if (stats)
            DEBUG_CCFOREST *stats << "using saved/found value" << endl;
    }
    else {
        if (stats)
            DEBUG_CCFOREST *stats << "not found" << endl;
        retRoot = i;
    }
    if (stats)
        DEBUG_CCFOREST *stats << "lookup for " << i << " gives " << retRoot
                              << "; saved = " << savedRoot << endl;
    return retRoot;
}

/* ---------------------------------------------------------------------- */

template <class T>
void ccforest<T>::printRootStream()
{
    findAllRoots(); /* find all the roots if not done */
    printStream(cout, rootStream);
}

template <class T>
void ccforest<T>::printEdgeStream()
{
    printStream(cout, edgeStream);
}

/* ---------------------------------------------------------------------- */

template class keyvalue<cclabel_type>;
template class keyCmpKeyvalueType<cclabel_type>;
template class ccforest<cclabel_type>;
