/* -*- 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 2, 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., 59 Temple Place - Suite 330,
 * Boston, MA 02111-1307, USA.
 */
/*
 * Copyright 2010, KA1RBI 
 */
/*
 * config.h is generated by configure.  It contains the results
 * of probing for features, options etc.  It should be the first
 * file included in your .cc file.
 */
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include <repeater_pipe.h>
#include <gr_io_signature.h>

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <errno.h>
#include <signal.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/ioctl.h>
#include <sys/time.h>

/*
 * Create a new instance of repeater_pipe and return
 * a boost shared_ptr.  This is effectively the public constructor.
 */
repeater_pipe_sptr 
repeater_make_pipe (size_t input_size, size_t output_size, const char* pathname, float io_ratio)
{
  return repeater_pipe_sptr (new repeater_pipe (input_size, output_size, pathname, io_ratio));
}

/*
 * The private constructor
 */
repeater_pipe::repeater_pipe (size_t input_size, size_t output_size, const char* pathname, float io_ratio)
  : gr_block ("pipe",
		   gr_make_io_signature ((input_size>0) ? 1 : 0, (input_size>0) ? 1 : 0, input_size),
		   gr_make_io_signature ((output_size>0) ? 1 : 0, (output_size>0) ? 1 : 0, output_size)),
    d_pid(0),
    d_e_in(0),
    d_e_out(0)
{

  d_input_size = input_size;
  d_output_size = output_size;
  d_io_ratio = io_ratio;
  d_pathname = pathname;

  switch(input_size) {
  case 0:
    d_e_in = 0;
    break;
  case 1:
    d_e_in = 1;
    break;
  case 2:
    d_e_in = 2;
    break;
  case 4:
    d_e_in = 3;
    break;
  case 8:
    d_e_in = 4;
    break;
  default:
    d_e_in = 0;
    fprintf(stderr, "error: repeater_pipe: input_size invalid\n");
    break;
  }
  switch(output_size) {
  case 0:
    d_e_out = 0;
    break;
  case 1:
    d_e_out = 1;
    break;
  case 2:
    d_e_out = 2;
    break;
  case 4:
    d_e_out = 3;
    break;
  case 8:
    d_e_out = 4;
    break;
  default:
    d_e_out = 0;
    fprintf(stderr, "error: repeater_pipe: output_size invalid\n");
    break;
  }

  // parent writes to fd_in[1],  reads from fd_out[0]
  // child  writes to fd_out[1], reads from fd_in[0]
  if (pipe(pipe_fd_in) == -1) {
    fprintf(stderr, "pipe: %s\n", strerror(errno));
    return;
  }
  if (pipe(pipe_fd_out) == -1) {
    fprintf(stderr, "pipe: %s\n", strerror(errno));
    return;
  }
  pid_t pid = fork();
  if (pid < 0) {
    fprintf(stderr, "fork: %s\n", strerror(errno));
    return;
  } else if (pid == 0) {  /* in child */
    // close unused fd's
    close(pipe_fd_in[1]);
    close(pipe_fd_out[0]);

    // setup stdin and stdout for child
    dup2(pipe_fd_in[0], 0);
    dup2(pipe_fd_out[1], 1);

    // close duped fd's
    close(pipe_fd_in[0]);
    close(pipe_fd_out[1]);

    system(d_pathname);

    exit(0);
  } /* end of if(in child) */
  /* in parent */
  d_pid = pid;

  // close unused fd's
  close(pipe_fd_in[0]);
  close(pipe_fd_out[1]);

  if (fcntl(pipe_fd_in[1], F_SETFL, O_NONBLOCK) == -1) {
    fprintf(stderr, "fcntl: %s\n", strerror(errno));
    return;
  }
#if 1
  if (fcntl(pipe_fd_out[0], F_SETFL, O_NONBLOCK) == -1) {
    fprintf(stderr, "fcntl: %s\n", strerror(errno));
    return;
  }
#endif
}

/*
 * Our virtual destructor.
 */
repeater_pipe::~repeater_pipe ()
{
  if (d_pid)
    kill (d_pid, SIGTERM);
}

void
repeater_pipe::forecast(int nof_output_items, gr_vector_int &nof_input_items_reqd)
{
   const size_t nof_inputs = nof_input_items_reqd.size();
//   const int nof_samples_reqd = (int)(d_io_ratio * nof_output_items) << 1;
   const int nof_samples_reqd = d_io_ratio * nof_output_items;
   std::fill(&nof_input_items_reqd[0], &nof_input_items_reqd[nof_inputs], nof_samples_reqd);
//   fprintf(stderr, "forecast nout %d nin %d sr %d\n", nof_output_items, nof_inputs, nof_samples_reqd);
}


int 
repeater_pipe::general_work (int noutput_items,
                               gr_vector_int &ninput_items,
                               gr_vector_const_void_star &input_items,
                               gr_vector_void_star &output_items)
{
  unsigned char *in;
  unsigned char *out;
  if (d_input_size > 0)
    in = (unsigned char *) input_items[0];
  if (d_output_size > 0)
    out = (unsigned char *) output_items[0];
  static const unsigned int e_masks[] = {0, 1, 3, 0xf, 0xff};
  static int tot1=0;
  static int tot2=0;
  struct timeval tv;
  struct timezone tz;
  gettimeofday(&tv, &tz);
#define min(a,b) ((a<b)?a:b)
//  fprintf(stderr, "nout %d, nin[0] %d\n", noutput_items, ninput_items[0]);

  //////////////////////////////////////////////////////////////////////
  // first, handle parent write from gr's source to child's stdin
  //////////////////////////////////////////////////////////////////////
  size_t amt = noutput_items * d_input_size;
  amt = min(amt, 512);
  amt = ((tot1 - tot2) > 512) ? 0 : amt;
  if (amt > 0) {
    int rc = write(pipe_fd_in[1], in, amt);
    if (rc < 0 && errno != EAGAIN) {
      fprintf(stderr, "write: %d: %s\n", errno, strerror(errno));
    }
    if (rc > 0) {
       // FIXME: handle if amt actually written not exact multiple of size
       // if not, for size 2, 4, or 8, bytewise alignment would be lost
//       if(rc & e_masks[d_e_in] != 0)
           fprintf(stderr, "%d amt %d rc %d d_e_in %d mask %x t1 %d t2 %d nin0 %d\n", (int)tv.tv_usec, amt, rc, d_e_in, e_masks[d_e_in], tot1, tot2, ninput_items[0]);

       // Tell runtime system how many input items we consumed on
       // each input stream.

       consume_each(rc >> (d_e_in - 1));
       tot1 += rc;
    }
  }

  //////////////////////////////////////////////////////////////////////
  // do parent read from child's stdout to gr's sink
  //////////////////////////////////////////////////////////////////////
  amt = noutput_items * d_output_size;
#if 1
  size_t read_amt=0;
  if (ioctl(pipe_fd_out[0], FIONREAD, &read_amt) == -1) {
      fprintf(stderr, "ioctl: %s\n", strerror(errno));
      return 0;
  }
  // fprintf(stderr, "%d read_amt: %d noutput %d\n", (int)tv.tv_usec, read_amt, noutput_items);
  amt = min(amt, read_amt);
#endif
  if (amt > 0) {
    int rc = read(pipe_fd_out[0], out, amt);
    if (rc < 0 && errno != EAGAIN)
      fprintf(stderr, "read: %s\n", strerror(errno));
    if (rc < 0)
      return 0;
    else if (rc == 0) { /* if EOF */
      fprintf(stderr, "tot1 %d tot2 %d\n", tot1, tot2);
      return 0;
    }
    // FIXME: check that amt is exact multiple of size
    // if not, for size 2, 4, or 8, bytewise alignment would be lost
 //   if (rc & e_masks[d_e_out] != 0)
       fprintf(stderr, "%d amt %d rc %d d_e_out %d mask %x t1 %d t2 %d\n", (int)tv.tv_usec, amt, rc, d_e_in, e_masks[d_e_in], tot1, tot2);
    tot2 += rc;
    return rc >> (d_e_out - 1);
  }

  // Tell runtime system how many output items we produced.
  return 0;
}
