/*
#  $Id$
# -----------------------------------------------------------------------------
#  The part of 'VideoNEXT MediaClient SDK'
# -----------------------------------------------------------------------------
#  Author: Petrov Maxim, 03/04/2009
#  Edited by:
#  QA by:
#  Copyright: videoNEXT LLC
# -----------------------------------------------------------------------------
*/

#include "Cache.h"

namespace videonext { namespace media {

Cache::Cache(UsageEnvironment& env, uint32_t maxSize, MemCounter *mc)
  : maxSize_(maxSize), current_(frameList_.begin()), memCounter_(mc)
{
}

Cache::~Cache()
{
}

void Cache::add(CPMediaFrame &cpMediaFrame, bool adjust)
{
    MutexGuard mutexGuard(mutex_);

    if (maxSize_ == 0)
        return;
    
    if (memCounter_->size() > maxSize_)
    {
        // remove frame from head
        if (current_ == frameList_.begin()) ++current_; 
        if(!frameList_.empty())
	   frameList_.erase(frameList_.begin());       
    }  
   
    frameList_.push_back(cpMediaFrame);

    memCounter_->inc(cpMediaFrame->size);

    if (adjust || current_ == frameList_.end()) // adjusting pointer to last valid frame (Usualy in case we are playing from server)
    {
        if (frameList_.size() == 1)
            current_ = frameList_.begin();
        else
            current_ = (++frameList_.rbegin()).base();
    }
    
}

CPMediaFrame Cache::head(CACHE_STATUS *status)
{
    MutexGuard mutexGuard(mutex_);
   
    *status = OK;
    
    if (frameList_.empty())
    {   
        *status = OUT_OF_BUFFER;
        return CPMediaFrame(NULL);
    }
    
    return *frameList_.begin();
}

CPMediaFrame Cache::tail(CACHE_STATUS *status)
{
    MutexGuard mutexGuard(mutex_);
    
    *status = OK;
    
    if (frameList_.empty())
    {
        *status = OUT_OF_BUFFER;
        return CPMediaFrame(NULL);
    }
    
    return *frameList_.rbegin();
}

CPMediaFrame Cache::advance(DIRECTION direction, CACHE_STATUS *status)
{
    MutexGuard mutexGuard(mutex_);
        
    *status = OK;
    
    if (direction >= 0) // forward
    {
        if (current_ == frameList_.end() || maxSize_ == 0
/*            || ++std::list<CPMediaFrame>::iterator(current_) == frameList_.end() */
           ) 
        {
            *status = OUT_OF_BUFFER;
            return CPMediaFrame(NULL);
        }
       
        if ((*current_)->markJump)
        {
            CPMediaFrame cpMediaFrame = *current_;            
            frameList_.erase(current_, frameList_.end());  
            current_ = frameList_.end();

            *status = JUMP2TS;
           
            return cpMediaFrame;
        }
   
        return *current_++;
    }
    else // backward
    { 
        // TODO: more right way to do not skip first frame
        if (current_ == frameList_.begin())
        {            
            *status = OUT_OF_BUFFER;
            return CPMediaFrame(NULL);
        }
        
        return *(--current_);
    }
}

bool Cache::jumpToTimestamp(int32_t timestamp, bool playingArchive)
{
    MutexGuard mutexGuard(mutex_);
    
    if (frameList_.empty()) return false;
    
    if (timestamp < (*frameList_.begin())->tv.tv_sec)
    {
        if (!playingArchive)
        {
            current_ = ++frameList_.begin();
        }
        else
        {
            frameList_.clear();
            current_ = frameList_.end();
        }
        return false;
    }
    
    if (timestamp > (*frameList_.rbegin())->tv.tv_sec)
    {        
        if (!playingArchive)
        {
            current_ = frameList_.end();
            return false;
        }
        
        // Marking last timestamp
        std::list<CPMediaFrame>::iterator pos = (++frameList_.rbegin()).base();
        int32_t currentSec = (*pos)->tv.tv_sec;
        for (;
             pos != frameList_.begin() && (*pos)->tv.tv_sec == currentSec; 
             --pos)
        {
            (*pos)->markJump = true;
        }
        if (pos == frameList_.begin() && (*pos)->tv.tv_sec == currentSec)
        {
            (*pos)->markJump = true;
        }
        
        current_ = frameList_.end();
        
        return false;
    }
    
    // Frame may or not my be in middle of buffer
    // We'll try to find it
    std::list<CPMediaFrame>::iterator pos = search_first(timestamp);
    if (pos != frameList_.end())
    {
        current_ = pos;
        
        return true;
    }
    else 
    {
        pos = search_first_more_than(timestamp);
        frameList_.erase(pos, frameList_.end());
        current_ = frameList_.end(); 
    } 
   
    return false;
}

bool Cache::boundaries(struct timeval *left, struct timeval *right)
{
    MutexGuard mutexGuard(mutex_);
 
    left->tv_sec = 0;
    left->tv_usec = 0;
 
    right->tv_sec = 0;
    right->tv_usec = 0;
  
    if (frameList_.empty() || maxSize_ == 0)
    {
        return false;
    }
    
    *left   = (*frameList_.begin())->tv;
    *right  = (*frameList_.rbegin())->tv;
    
    return true;
}
 
void Cache::markJump()
{
    MutexGuard mutexGuard(mutex_);

    if (maxSize_ == 0)
        return;
    
    if (current_ == frameList_.end()) return;
    
    // marking whole second
    int32_t currentSec = (*current_)->tv.tv_sec;
   
    std::list<CPMediaFrame>::iterator pos;
    for (pos = current_;
         pos != frameList_.end() && (*pos)->tv.tv_sec == currentSec; 
         ++pos)
    {
        (*pos)->markJump = true;
    }
    
    for (pos = current_;
         pos != frameList_.begin() && (*pos)->tv.tv_sec == currentSec; 
         --pos)
    {
        (*pos)->markJump = true;
    }
    if (pos == frameList_.begin() && (*pos)->tv.tv_sec == currentSec)
    {
        (*pos)->markJump = true;
    }    
}

std::list<CPMediaFrame>::iterator Cache::search_first(int32_t timestamp)
{
    int left = 0;
    int right = frameList_.size();
    int mid = 0;

    if (maxSize_ == 0)
        return frameList_.end(); 
    
    while (left <= right)
    {
        std::list<CPMediaFrame>::iterator pos = frameList_.begin();
        
        mid = (left+right) / 2;
        
        std::advance(pos, mid);
 
        if (timestamp > (*pos)->tv.tv_sec)
        {
            left = ++mid;
        }
        else if (timestamp  < (*pos)->tv.tv_sec)
        {
            right = --mid;
        }
        else // found
        {
            if (mid == 0) return frameList_.begin();
          
            pos = frameList_.begin(); std::advance(pos, mid);     
            
            do
            {
                --pos;    
                if ((*pos)->tv.tv_sec != timestamp) 
                {
                    ++pos;
                    break;
                }
                                
            } while (pos != frameList_.begin());
                    
            return pos;
        }
    }
    
    return frameList_.end();
}

std::list<CPMediaFrame>::iterator Cache::search_last(int32_t timestamp)
{
    int left = 0;
    int right = frameList_.size();
    int mid = 0;

    if (maxSize_ == 0)
        return frameList_.end(); 
    
    while (left <= right)
    {
        std::list<CPMediaFrame>::iterator pos = frameList_.begin();
        
        mid = (left+right) / 2;
        
        std::advance(pos, mid);
 
        if (timestamp > (*pos)->tv.tv_sec)
        {
            left = ++mid;
        }
        else if (timestamp  < (*pos)->tv.tv_sec)
        {
            right = --mid;
        }
        else // found
        {
            if (mid == 0) return frameList_.begin();
          
            pos = frameList_.begin(); std::advance(pos, mid);   
            
            do
            {
                ++pos;    
                if ((*pos)->tv.tv_sec != timestamp) 
                {
                    --pos;
                    break;
                }
                                
            } while (pos != frameList_.end());
                    
            return pos;
        }
    }
    
    return frameList_.end();
}


std::list<CPMediaFrame>::iterator Cache::search_first_more_than(int32_t timestamp)
{
    int left = 0;
    int right = frameList_.size();
    int mid = 0;

    if (maxSize_ == 0)
        return frameList_.end(); 

    
    std::list<CPMediaFrame>::iterator first_more_than = frameList_.end();
    
    while (left <= right)
    {
        std::list<CPMediaFrame>::iterator pos = frameList_.begin();
        
        mid = (left+right) / 2;
        
        std::advance(pos, mid);
 
        if (timestamp > (*pos)->tv.tv_sec)
        {
            left = ++mid;
        }
        else if (timestamp  < (*pos)->tv.tv_sec)
        {
            first_more_than = pos;
            right = --mid;            
        }
    }
    
    if (first_more_than == frameList_.end()) return frameList_.end();
    if (first_more_than == frameList_.begin()) return frameList_.begin();
                        
    int32_t currentTimestamp = (*first_more_than)->tv.tv_sec;
                           
    do
    {
        --first_more_than;    
        if ((*first_more_than)->tv.tv_sec != currentTimestamp) 
        {
            ++first_more_than;
            break;
        }
                                
    } while (first_more_than != frameList_.begin());
                    
    return first_more_than;
      
}

}}


