#include "zcav_io.h"

#ifdef WIN32
#include <io.h>
#endif

#ifndef NON_UNIX
#include <unistd.h>
#include <sys/resource.h>
#include <sys/time.h>
#endif
#include <time.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
#include <string.h>

ZcavRead::~ZcavRead()
{
  delete m_name;
}

int ZcavRead::Open(bool *finished, int block_size
                 , const char *file, const char *log)
{
  m_name = strdup(file);
  m_finished = finished;
  m_block_size = block_size;

  if(strcmp(file, "-"))
  {
    m_fd = file_open(file, O_RDONLY);
    if(m_fd == -1)
    {
      fprintf(stderr, "Can't open %s\n", file);
      return 1;
    }
  }
  else
  {
    m_fd = 0;
  }
  if(strcmp(log, "-"))
  {
    m_logFile = true;
    m_log = fopen(log, "w");
    if(m_log == NULL)
    {
      fprintf(stderr, "Can't open %s\n", log);
      file_close(m_fd);
      return 1;
    }
  }
  else
  {
    m_logFile = false;
    m_log = stdout;
  }
  return 0;
}

void ZcavRead::Close()
{
  if(m_logFile)
    fclose(m_log);
  if(m_fd != 0)
    ::close(m_fd);
}

int ZcavRead::writeStatus(int fd, char c)
{
  if(write(fd, &c, 1) != 1)
  {
    fprintf(stderr, "Write channel broken\n");
    return 1;
  }
  return 0;
}

int ZcavRead::Read(int max_loops, int max_size, int writeCom)
{
  int i;
  bool exiting = false;
  for(int loops = 0; !exiting && loops < max_loops; loops++)
  {
    if(lseek(m_fd, 0, SEEK_SET))
    {
      fprintf(stderr, "Can't llseek().\n");
      writeStatus(writeCom, eSEEK);
      return 1;
    }
    // i is block index
    bool nextLoop = false;
    for(i = 0; !nextLoop && (!max_size || i < max_size) && (loops == 0 || m_times[i][0] != -1.0); i++)
    {
      if(loops == 0)
        m_times.push_back(new double[max_loops]);
      double read_time = readmegs();
      m_times[i][loops] = read_time;
      if(read_time < 0.0)
      {
        if(i == 0)
        {
          fprintf(stderr, "Input file \"%s\" too small.\n", m_name);
          writeStatus(writeCom, eSIZE);
          return 1;
        }
        nextLoop = true;
      }
      if(loops == 0)
        m_count.push_back(0);
      m_count[i]++;
    } // end loop for reading blocks
    if(exiting)
      return 1;
  } // end loop for multiple disk reads
  fprintf(m_log, "#loops: %d\n", max_loops);
  fprintf(m_log, "#block K/s time\n");
//  for(i = 0; (!max_size || i < max_size) && m_count[i]; i++)
  for(i = 0; m_times[i][0] != -1.0; i++)
  {
    printavg(i, average(m_times[i], m_count[i]), m_block_size);
  }
  writeStatus(writeCom, eEND);
  return 0;
}

void ZcavRead::printavg(int position, double avg, int block_size)
{
  double num_k = double(block_size * 1024);
  if(avg < 1.0)
    fprintf(m_log, "#%d ++++ %f\n", position * block_size, avg);
  else
    fprintf(m_log, "%d %d %f\n", position * block_size, int(num_k / avg), avg);
}

int compar(const void *a, const void *b)
{
  double *c = (double *)(a);
  double *d = (double *)(b);
  if(*c < *d) return -1;
  if(*c > *d) return 1;
  return 0;
}

// Returns the mean of the values in the array.  If the array contains
// more than 2 items then discard the highest and lowest thirds of the
// results before calculating the mean.
double average(double *array, int count)
{
  qsort(array, count, sizeof(double), compar);
  int skip = count / 3;
  int arr_items = count - (skip * 2);
  double total = 0.0;
  for(int i = skip; i < (count - skip); i++)
  {
    total += double(array[i]);
  }
  return total / double(arr_items);
}

// just like the read() system call but uses a member for the buffer and will
// not return a partial result.
ssize_t ZcavRead::readall(int count)
{
  ssize_t total = 0;
  while(total != static_cast<ssize_t>(count) )
  {
    ssize_t rc = file_read(m_fd, &m_buf[total], count - total);
    if(rc == -1 || rc == 0)
      return -1;
    total += rc;
  }
  return total;
}

// Read the specified number of megabytes of data from the fd and return the
// amount of time elapsed in seconds.
double ZcavRead::readmegs()
{
  m_dur.start();

  for(int i = 0; i < m_block_size; i++)
  {
    int rc = readall(meg);
    if(rc != meg)
      return -1.0;
  }
  return m_dur.stop();
}