// ****************************************************************************
//  Method: avtDatasetOnDemandFilter::GetLoadedDomains
//
//  Purpose:
//      Return a list of domains that are loaded.
//
//  Programmer: Dave Pugmire
//  Creation:   March 19, 2008
//
//  Modifications:
//
//    Dave Pugmire, Tue Mar 10 12:41:11 EDT 2009
//    Added support for time/domain.
//
// ****************************************************************************
void
avtDatasetOnDemandFilter::GetLoadedDomains(std::vector<std::vector<int> > &domains)
{
    if (DebugStream::Level1())
    {
        debug1<<"avtDatasetOnDemandFilter::GetLoadedDomains()\n";
    }
    if ( ! OperatingOnDemand() )
        EXCEPTION0(ImproperUseException);


    domains.resize(0);
    std::list<DomainCacheEntry>::const_iterator it;
    for ( it = domainQueue.begin(); it != domainQueue.end(); it++ )
    {
        std::vector<int> dom(2);
        dom[0] = it->domainID;
        dom[1] = it->timeStep;
        domains.push_back(dom);
    }
}
vtkDataSet *
avtDatasetOnDemandFilter::GetDomain(int domainId,
                                    int timeStep)
{
    if (DebugStream::Level5())
    {
        debug5<<"avtDatasetOnDemandFilter::GetDomain("<<domainId<<", "<<timeStep<<");"<<endl;
    }
    if ( ! OperatingOnDemand() )
        EXCEPTION0(ImproperUseException);

    if ( domainId < 0 )
        return NULL;

    // See if it is already in the cache.  If so, just return it.
    std::list<DomainCacheEntry>::iterator it;
    for ( it = domainQueue.begin(); it != domainQueue.end(); it++ )
    {
        // Found it. Move it to the front of the list.
        if (it->domainID == domainId &&
            it->timeStep == timeStep)
        {
            DomainCacheEntry entry;
            entry = *it;
            //Remove, then move to front.
            domainQueue.erase( it );
            domainQueue.push_front( entry );
            return entry.ds;
        }
    }

    avtSILRestriction_p silr;
    if (timeStep == firstContract->GetDataRequest()->GetTimestep())
    {
        silr = firstContract->GetDataRequest()->GetRestriction();
    }
    else if ((*lastUsedContract != NULL) && 
             (timeStep == lastUsedContract->GetDataRequest()->GetTimestep()))
    {
        silr = lastUsedContract->GetDataRequest()->GetRestriction();
    }
    else
    {
        // The SIL restriction associated with the contract may be for the wrong
        // time step.  Go get the correct one.
        std::string db = GetInput()->GetInfo().GetAttributes().GetFullDBName();
        ref_ptr<avtDatabase> dbp = avtCallback::GetDatabase(db, 0, NULL);
        if (*dbp == NULL)
            EXCEPTION1(InvalidFilesException, db.c_str());

        std::string mesh = GetInput()->GetInfo().GetAttributes().GetMeshname();
        avtDataObject_p dob = dbp->GetOutput(mesh.c_str(), timeStep);
        lastUsedContract = dob->GetOriginatingSource()->GetGeneralContract();
        silr = lastUsedContract->GetDataRequest()->GetRestriction();
   }

    avtDataRequest_p new_dr = new avtDataRequest(firstContract->GetDataRequest(), silr);
    avtContract_p new_contract = new avtContract(firstContract, new_dr);

    if (DebugStream::Level5())
    {
        debug5<<"     Update->GetDomain "<<domainId<<" time= "<<timeStep<<endl;
    }
    std::vector<int> domains;
    domains.push_back(domainId);
    new_contract->GetDataRequest()->GetRestriction()->TurnOnAll();
    new_contract->GetDataRequest()->GetRestriction()->RestrictDomains(domains);
    new_contract->GetDataRequest()->SetTimestep(timeStep);
    new_contract->SetOnDemandStreaming(true);

    GetInput()->Update(new_contract);
    vtkDataSet *rv = GetInputDataTree()->GetSingleLeaf();
    if (rv == NULL)
    {
        // This issue has been known to occur when: 
        //  -- the SIL is time varying
        //  -- the domain requested doesn't exist for the initial time step
        //     (which is the one where the SIL is created from).
        EXCEPTION1(VisItException, "Failure retrieving a data set while "
                     "advecting particles.  Please report this to a VisIt "
                     "developer.");
    }

    // Add it to the cache.
    DomainCacheEntry entry;
    entry.domainID = domainId;
    entry.timeStep = timeStep;
    entry.ds = rv;
    rv->Register(NULL);
    loadDSCount++;

    //Update the domainLoadCount.
    //Turn two ints into a long. Put timeStep in upper, domain in lower.
    unsigned long long A =  (((unsigned long long)timeStep)<<32);
    unsigned long long B =  ((unsigned long long)domainId);
    unsigned long long idx = A | B;

    if (domainLoadCount.find(idx) == domainLoadCount.end())
    {
        domainLoadCount[idx] = 1;
    }
    else
    {
        domainLoadCount[idx] ++;
    }

    domainQueue.push_front(entry);
    if ( domainQueue.size() > (size_t)maxQueueLength )
    {
        DomainCacheEntry tmp = domainQueue.back();
        PurgeDomain( tmp.domainID, tmp.timeStep );
        domainQueue.pop_back();
        purgeDSCount++;
    }

    return rv;
}
vtkDataSet *
avtDatasetOnDemandFilter::GetDataAroundPoint(double X, double Y, double Z,
                                             int timeStep)
{
    if (DebugStream::Level1()) 
    {
        debug1<<"avtDatasetOnDemandFilter::GetDataAroundPoint("<<X<<", "<<Y<<", "<<Z<<", "<<timeStep<<");"<<endl;
    }
    if ( ! OperatingOnDemand() )
    {
        EXCEPTION0(ImproperUseException);
    }

    int domainId = 0; //Need to hash XYZ to domainId ???
    // FIXME: For the moment we just use one domain ID (0) for all points. This choice will cause
    // the following for loop to test *all* cache entries whether they contain the point location.
    // This strategy is not very efficient, but better than a pipeline re-execute.

    if (DebugStream::Level5())
    {
        debug5<<"Look in cache: "<<domainId<<" sz= "<<domainQueue.size()<<endl;
    }
    //See if it's in the cache.
    std::list<DomainCacheEntry>::iterator it;
    int foundPos = 0;
    for ( it = domainQueue.begin(); it != domainQueue.end(); it++ )
    {
        // Found it. Move it to the front of the list.
        if (it->domainID == domainId &&
            it->timeStep == timeStep)
        {
            //Do a bbox check.
            double bbox[6];
            it->ds->GetBounds(bbox);
            if (DebugStream::Level5()) 
            {
                debug5<<"BBOX ["<<bbox[0]<<", "<<bbox[1]<<"]["<<bbox[2]<<", "<<bbox[3]<<"]["<<bbox[4]<<", "<<bbox[5]<<"]"<<endl;
            }
            if (! (X >= bbox[0] && X <= bbox[1] &&
                   Y >= bbox[2] && Y <= bbox[3] &&
                   Z >= bbox[4] && Z <= bbox[5]))
                continue;
            
            bool foundIt = false;
            
            // If rectilinear, we found the domain.
            if (it->ds->GetDataObjectType() == VTK_RECTILINEAR_GRID)
                foundIt = true;
            else
            {
                //Do a cell check....
                if (DebugStream::Level5())
                {
                    debug5<<"It's in the bbox. Check the cell.\n";
                }
                vtkVisItCellLocator *cellLocator = it->cl;
                if ( cellLocator == NULL )
                {
                    cellLocator = vtkVisItCellLocator::New();
                    cellLocator->SetDataSet(it->ds);
                    cellLocator->IgnoreGhostsOn();
                    cellLocator->BuildLocator();
                    it->cl = cellLocator;
                }
                
                double rad = 1e-6, dist=0.0;
                double p[3] = {X,Y,Z}, resPt[3]={0.0,0.0,0.0};
                vtkIdType foundCell = -1;
                int subId = 0;

                if (cellLocator->FindClosestPointWithinRadius(p, rad, resPt,
                                                              foundCell, subId, dist))
                {
                    foundIt = true;
                    if (DebugStream::Level5())
                    {
                        debug5<<"Cell locate: We found the domain!\n";
                    }
                }
            }

            if (foundIt)
            {
                if (DebugStream::Level5())
                {
                    debug5<<"Found data in cace, returning cache entry " << foundPos << std::endl;
                }
                DomainCacheEntry entry;
                entry = *it;

                //Remove, then move to front.
                domainQueue.erase( it );
                domainQueue.push_front( entry );
                return entry.ds;
            }
        }
    }

    if (DebugStream::Level5())
    {
        debug5<<"     Update->GetDataAroundPoint, time= "<<timeStep<<endl;
    }
    avtContract_p new_contract = new avtContract(firstContract);
    new_contract->GetDataRequest()->GetRestriction()->TurnOnAll();
    avtPointSelection *ptsel = new avtPointSelection;
    double p[3] = { X, Y, Z };
    ptsel->SetPoint(p);

    // data selection will be deleted by contract.
    new_contract->GetDataRequest()->AddDataSelection(ptsel);

    new_contract->GetDataRequest()->SetTimestep(timeStep);
    new_contract->SetOnDemandStreaming(true);

    GetInput()->Update(new_contract);
    vtkDataSet *rv = GetInputDataTree()->GetSingleLeaf();

    if( rv )
    {
        // Add it to the cache.
        DomainCacheEntry entry;
        entry.domainID = domainId;
        entry.timeStep = timeStep;
        entry.ds = rv;
        entry.cl = NULL;
        rv->Register(NULL);
        loadDSCount++;

        domainQueue.push_front(entry);
        if ( domainQueue.size() > (size_t)maxQueueLength )
        {
            DomainCacheEntry tmp = domainQueue.back();
            PurgeDomain( tmp.domainID, tmp.timeStep );
            domainQueue.pop_back();
            purgeDSCount++;
        }
    }

    return rv;
}
vtkDataSet *
avtDatasetOnDemandFilter::GetDomain(int domainId,
                                    int timeStep)
{
    debug5<<"avtDatasetOnDemandFilter::GetDomain("<<domainId<<", "<<timeStep<<");"<<endl;
    if ( ! OperatingOnDemand() )
        EXCEPTION0(ImproperUseException);

    if ( domainId < 0 )
        return NULL;

    // See if it is already in the cache.  If so, just return it.
    std::list<DomainCacheEntry>::iterator it;
    for ( it = domainQueue.begin(); it != domainQueue.end(); it++ )
        // Found it. Move it to the front of the list.
        if (it->domainID == domainId &&
            it->timeStep == timeStep)
        {
            DomainCacheEntry entry;
            entry = *it;
            //Remove, then move to front.
            domainQueue.erase( it );
            domainQueue.push_front( entry );
            return entry.ds;
        }


    debug5<<"     Update->GetDomain "<<domainId<<" time= "<<timeStep<<endl;
    avtContract_p new_contract = new avtContract(firstContract);
    vector<int> domains;
    domains.push_back(domainId);
    new_contract->GetDataRequest()->GetRestriction()->TurnOnAll();
    new_contract->GetDataRequest()->GetRestriction()->RestrictDomains(domains);
    if (timeStep >= 0)
        new_contract->GetDataRequest()->SetTimestep(timeStep);
    new_contract->SetOnDemandStreaming(true);

    GetInput()->Update(new_contract);
    vtkDataSet *rv = GetInputDataTree()->GetSingleLeaf();

    // Add it to the cache.
    DomainCacheEntry entry;
    entry.domainID = domainId;
    entry.timeStep = timeStep;
    entry.ds = rv;
    rv->Register(NULL);
    loadDSCount++;

    //Update the domainLoadCount.
    //Turn two ints into a long. Put timeStep in upper, domain in lower.
    unsigned long long A =  (((unsigned long long)timeStep)<<32);
    unsigned long long B =  ((unsigned long long)domainId);
    unsigned long long idx = A | B;

    if (domainLoadCount.find(idx) == domainLoadCount.end())
        domainLoadCount[idx] = 0;
    domainLoadCount[idx] ++;

    domainQueue.push_front(entry);
    if ( domainQueue.size() > maxQueueLength )
    {
        domainQueue.pop_back();
        purgeDSCount++;
    }

    return rv;
}