/* -*- c++ -*- */
/*
 * Copyright 2004 Free Software Foundation, Inc.
 * 
 * This file is part of GNU Radio
 * 
 * GNU Radio 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 3, or (at your option)
 * any later version.
 * 
 * GNU Radio 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.
 * 
 * You should have received a copy of the GNU General Public License
 * along with GNU Radio; see the file COPYING.  If not, write to
 * the Free Software Foundation, Inc., 51 Franklin Street,
 * Boston, MA 02110-1301, USA.
 */

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <qa_gr_buffer.h>
#include <gr_buffer.h>
#include <cppunit/TestAssert.h>
#include <stdlib.h>
#include <gr_random.h>

static void
leak_check (void f ())
{
  long	buffer_count = gr_buffer_ncurrently_allocated ();
  long	buffer_reader_count = gr_buffer_reader_ncurrently_allocated ();

  f ();

  CPPUNIT_ASSERT_EQUAL (buffer_reader_count, gr_buffer_reader_ncurrently_allocated ());
  CPPUNIT_ASSERT_EQUAL (buffer_count, gr_buffer_ncurrently_allocated ());
}


// ----------------------------------------------------------------------------
// test single writer, no readers...
//

static void
t0_body ()
{
  int	nitems = 4000 / sizeof (int);
  int	counter = 0;

  gr_buffer_sptr buf(gr_make_buffer(nitems, sizeof (int), gr_block_sptr()));

  int last_sa;
  int sa;

  sa = buf->space_available ();
  CPPUNIT_ASSERT (sa > 0);
  last_sa = sa;

  for (int i = 0; i < 5; i++){
    sa = buf->space_available ();
    CPPUNIT_ASSERT_EQUAL (last_sa, sa);
    last_sa = sa;
    
    int *p = (int *) buf->write_pointer ();
    CPPUNIT_ASSERT (p != 0);

    for (int j = 0; j < sa; j++)
      *p++ = counter++;

    buf->update_write_pointer (sa);
  }
}

// ----------------------------------------------------------------------------
// test single writer, single reader
//

static void 
t1_body ()
 {
  int	nitems = 4000 / sizeof (int);
  int	write_counter = 0;
  int	read_counter = 0;

  gr_buffer_sptr buf(gr_make_buffer(nitems, sizeof (int), gr_block_sptr()));
  gr_buffer_reader_sptr r1 (gr_buffer_add_reader (buf, 0, gr_block_sptr()));
  

  int sa;

  // write 1/3 of buffer

  sa = buf->space_available ();
  CPPUNIT_ASSERT (sa > 0);

  int *p = (int *) buf->write_pointer ();
  CPPUNIT_ASSERT (p != 0);

  for (int j = 0; j < sa/3; j++){
    *p++ = write_counter++;
  }
  buf->update_write_pointer (sa/3);

  
  // write the next 1/3 (1/2 of what's left)

  sa = buf->space_available ();
  CPPUNIT_ASSERT (sa > 0);

  p = (int *) buf->write_pointer ();
  CPPUNIT_ASSERT (p != 0);

  for (int j = 0; j < sa/2; j++){
    *p++ = write_counter++;
  }
  buf->update_write_pointer (sa/2);


  // check that we can read it OK

  int ia = r1->items_available ();
  CPPUNIT_ASSERT_EQUAL (write_counter, ia);
  
  int *rp = (int *) r1->read_pointer ();
  CPPUNIT_ASSERT (rp != 0);

  for (int i = 0; i < ia/2; i++){
    CPPUNIT_ASSERT_EQUAL (read_counter, *rp);
    read_counter++;
    rp++;
  }
  r1->update_read_pointer (ia/2);

  // read the rest

  ia = r1->items_available ();
  rp = (int *) r1->read_pointer ();
  CPPUNIT_ASSERT (rp != 0);

  for (int i = 0; i < ia; i++){
    CPPUNIT_ASSERT_EQUAL (read_counter, *rp);
    read_counter++;
    rp++;
  }
  r1->update_read_pointer (ia);
}

// ----------------------------------------------------------------------------
// single writer, single reader: check wrap-around
//

static void
t2_body ()
{
  // 64K is the largest granularity we've seen so far (MS windows file mapping).
  // This allows a bit of "white box testing"
  
  int	nitems = (64 * (1L << 10)) / sizeof (int);	// 64K worth of ints

  gr_buffer_sptr buf(gr_make_buffer (nitems, sizeof (int), gr_block_sptr()));
  gr_buffer_reader_sptr r1 (gr_buffer_add_reader (buf, 0, gr_block_sptr()));

  int	read_counter = 0;
  int	write_counter = 0;
  int	n;
  int	*wp = 0;
  int	*rp = 0;
  
  // Write 3/4 of buffer

  n = (int) (buf->space_available () * 0.75);
  wp = (int *) buf->write_pointer ();

  for (int i = 0; i < n; i++)
    *wp++ = write_counter++;
  buf->update_write_pointer (n);

  // Now read it all

  int m = r1->items_available ();
  CPPUNIT_ASSERT_EQUAL (n, m);
  rp = (int *) r1->read_pointer ();

  for (int i = 0; i < m; i++){
    CPPUNIT_ASSERT_EQUAL (read_counter, *rp);
    read_counter++;
    rp++;
  }
  r1->update_read_pointer (m);

  // Now write as much as we can.
  // This will wrap around the buffer

  n = buf->space_available ();
  CPPUNIT_ASSERT_EQUAL (nitems - 1, n);		// white box test
  wp = (int *) buf->write_pointer ();

  for (int i = 0; i < n; i++)
    *wp++ = write_counter++;
  buf->update_write_pointer (n);
  
  // now read it all

  m = r1->items_available ();
  CPPUNIT_ASSERT_EQUAL (n, m);
  rp = (int *) r1->read_pointer ();

  for (int i = 0; i < m; i++){
    CPPUNIT_ASSERT_EQUAL (read_counter, *rp);
    read_counter++;
    rp++;
  }
  r1->update_read_pointer (m);

}

// ----------------------------------------------------------------------------
// single writer, N readers, randomized order and lengths
// ----------------------------------------------------------------------------

static void
t3_body ()
{
  int	nitems = (64 * (1L << 10)) / sizeof (int);

  static const int N = 5;
  gr_buffer_sptr buf(gr_make_buffer(nitems, sizeof (int), gr_block_sptr()));
  gr_buffer_reader_sptr 	reader[N];
  int			read_counter[N];
  int			write_counter = 0;
  gr_random		random;

  for (int i = 0; i < N; i++){
    read_counter[i] = 0;
    reader[i] = gr_buffer_add_reader (buf, 0, gr_block_sptr());
  }

  for (int lc = 0; lc < 1000; lc++){

    // write some

    int n = (int) (buf->space_available () * random.ran1 ());
    int *wp = (int *) buf->write_pointer ();

    for (int i = 0; i < n; i++)
      *wp++ = write_counter++;

    buf->update_write_pointer (n);

    // pick a random reader and read some

    int r = (int) (N * random.ran1 ());
    CPPUNIT_ASSERT (0 <= r && r < N);

    int m = reader[r]->items_available ();
    int *rp = (int *) reader[r]->read_pointer ();

    for (int i = 0; i < m; i++){
      CPPUNIT_ASSERT_EQUAL (read_counter[r], *rp);
      read_counter[r]++;
      rp++;
    }
    reader[r]->update_read_pointer (m);
  }
}


// ----------------------------------------------------------------------------

void
qa_gr_buffer::t0 ()
{
  leak_check (t0_body);
}

void
qa_gr_buffer::t1 ()
{
  leak_check (t1_body);
}

void
qa_gr_buffer::t2 ()
{
  leak_check (t2_body);
}

void
qa_gr_buffer::t3 ()
{
  leak_check (t3_body);
}

void
qa_gr_buffer::t4 ()
{
}

void
qa_gr_buffer::t5 ()
{
}
