

#ifndef VALIB_FILTER_TESTER_H
#define VALIB_FILTER_TESTER_H

#include "filter.h"
#include "log.h"

class FilterTester : public Filter
{
protected:
  Filter *filter;
  Log    *log;

  Speakers spk_input;  
  Speakers spk_output; 

  bool     stream;     
  bool     flushing;   

  int      streams;    

  void update_formats()
  {
    spk_input = filter->get_input();
    spk_output = filter->get_output();
  }

  void check_formats(const char *caller)
  {
    
    if (filter->get_input() != spk_input)
      log->err("[k2] %s: input format was illegaly changed", caller);
    spk_input = filter->get_input(); 

    
    if (filter->is_ofdd())
    {
      if (stream && filter->get_output() != spk_output)
        log->err("[x] %s: output format was illegaly changed", caller);
    }
    else
    {
      if (filter->get_output() != spk_output)
        log->err("[f2] %s: output format was illegaly changed", caller);
    }
    spk_output = filter->get_output(); 

    
    if ((filter->get_input() == spk_unknown) && !filter->is_empty())
      log->err("[f5] %s: filter is not empty in uninitialized state", caller);
  }

  void check_reset(const char *caller)
  {
    if (filter->is_ofdd() && (filter->get_output() != spk_unknown))
      log->err("[f1] %s: output format did not change to spk_unknown", caller);

    if (!filter->is_empty())
      log->err("[f5] %s: filter is not empty", caller);

    
  }

public:
  FilterTester()
  {
    filter = 0;
    log = 0;
  }

  FilterTester(Filter *_filter, Log *_log)
  {
    filter = _filter;
    log = _log;
    update_formats();
  }

  void link(Filter *_filter, Log *_log)
  {
    filter = _filter;
    log = _log;
    update_formats();
  }

  void unlink()
  {
    filter = 0;
    log = 0;
  }

  void reset_streams()     { streams = 0;    }
  int  get_streams() const { return streams; }

  void reset()
  {
    if (!filter) return;

    stream = false;
    flushing = false;

    check_formats("before reset()");

    filter->reset();

    check_reset("after reset()");
    check_formats("after reset()");
  }

  bool is_ofdd() const
  {
    if (!filter) return false;
    return filter->is_ofdd();
  }

  
  

  bool query_input(Speakers _spk) const 
  {
    if (!filter) return false;
    return filter->query_input(_spk);
  }

  bool set_input(Speakers _spk)
  {
    if (!filter) return false;

    stream = false;
    flushing = false;

    check_formats("before set_input()");

    bool query = filter->query_input(_spk);
    bool result = filter->set_input(_spk);

    if (query != result)
      log->err("[k3] set_input(): query_input() lies");

    if (result)
    {
      
      
      if (filter->get_input() != _spk)
        log->err("[k4] set_input(): new format was not set");
      update_formats();
    }
    else
    {
      
      
      if (filter->get_input() == spk_unknown)
        
        update_formats();
      else
        
        check_formats("after set_input()");
    }

    
    check_reset("after set_input()"); 
    return result;
  }

  Speakers get_input() const
  {
    if (!filter) return spk_unknown;
    return filter->get_input();
  }

  bool process(const Chunk *_chunk)
  {
    if (!filter) return false;
    bool dummy = false;
    bool input_format_change = false;
    bool query = true;

    
    if (!_chunk)
    {
      log->err("process(): null chunk pointer!!!");
      return false;
    }

    
    if (!is_empty())
    {
      log->err("process() is called in full state");
      return false;
    }

    
    if (_chunk->is_dummy())
    {
      
      
      dummy = true;
    }
    else if (_chunk->spk != get_input())
    {
      input_format_change = true;
      query = filter->query_input(_chunk->spk);
    }

    check_formats("before process()");

    bool result = filter->process(_chunk);

    if (input_format_change)
    {
      stream = false;
      flushing = false;

      if (query != result)
        log->err("[k3] process(): query_input() lies");

      if (result)
      {
        
        
        if (filter->get_input() != _chunk->spk)
          log->err("[k4] process(): new format was not set");

        
        
        
        
        if (_chunk->is_empty() && !_chunk->eos)
          check_reset("process()");

        update_formats();
      }
      else
      {
        
        
        if (filter->get_input() == spk_unknown)
          
          update_formats();
        else
          
          check_formats("after set_input()");
      }
    }
    else
    {
      if (!result && filter->get_input() == spk_unknown)
        
        update_formats();
      else 
        
        check_formats("after process()");
    }

    
    if (dummy && !is_empty())
      log->err("process(): filter is not empty after dummy chunk processing");

    
    if (!is_empty())
      stream = true;

    
    if (_chunk->eos && stream && !dummy)
      if (is_empty())
        log->err("process(): filter does not want to end the stream");
      else
        flushing = true;

    return result;
  }

  
  

  Speakers get_output() const
  {
    if (!filter) return spk_unknown;
    return filter->get_output();
  }

  bool is_empty() const
  {
    if (!filter) return true;
    return filter->is_empty();
  }

  bool get_chunk(Chunk *_chunk)
  {
    if (!filter) return false;

    
    if (!_chunk)
    {
      log->err("get_chunk(): null chunk pointer!!!");
      return false;
    }

    
    if (filter->is_empty())
    {
      log->err("get_chunk() is called in empty state");
      return false;
    }

    check_formats("before get_chunk()");

    Speakers spk = filter->get_output();

    if (!filter->get_chunk(_chunk))
      return false;

    if (!_chunk->is_dummy())
    {
      if (_chunk->spk != spk)
        log->err("[s1] get_chunk(): get_output() lies");

      
      if (_chunk->eos)
      {
        stream = false;
        streams++;
      }

      check_formats("after get_chunk()");

      
      if (!is_empty())
        stream = true;

      
      if (flushing && stream && is_empty())
        log->err("get_chunk(): filter did not end the stream");
    }
    else
    {
      
      check_formats("after get_chunk()");
    }

    return true;
  }
};

#endif
