/*****************************************************************************\
*                                                                             *
*  Name   : io_to_file                                                        *
*  Author : Chris Koeritz                                                     *
*                                                                             *
*  Purpose:                                                                   *
*                                                                             *
*    Runs an application with I/O redirected to a file specified on the       *
*  command line.  For example:                                                *
*       io_to_file my_output_file.txt diff qubert.c rugbert.c                 *
*  will create an output file called "output_file.txt" and run the remaining  *
*  parameters as a subshell with standard output redirected to that file.     *
*                                                                             *
*******************************************************************************
* Copyright (c) 2007-$now By Author.  This program 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 of   *
* the License or (at your option) any later version.  This is online at:      *
*     http://www.fsf.org/copyleft/gpl.html                                    *
* Please send any updates to: fred@gruntose.com                               *
\*****************************************************************************/

#define INCLUDE_IOTOFILE_CODE
#include "extra_source_tiny.cpp"

#include <basis/byte_array.h>
#include <basis/istring.h>
#include <basis/log_base.h>
#include <basis/object_base.h>
#include <basis/utility.h>
#include <mechanisms/ithread.h>
#include <opsystem/command_line.h>
#include <opsystem/filename.h>
#include <opsystem/redirecter.h>
#include <opsystem/startup_code.h>
#include <processes/shutdown_alerter.h>
#include <textual/byte_format.h>

#include <stdio.h>

const int READ_INTERVAL = 28;
  // how frequently our thread looks for data.

#undef LOG
#define LOG(to_print) CLASS_EMERGENCY_LOG(program_wide_logger(), to_print)
#undef BASE_LOG
#define BASE_LOG(to_print) program_wide_logger().log(to_print)

HOOPLE_STARTUP_CODE;

////////////////////////////////////////////////////////////////////////////

class text_grabber : public ithread
{
public:
  text_grabber(byte_filer &output_file, const istring &command,
      const istring &parms)
  : ithread(READ_INTERVAL), _file(output_file),
    _stdio(),
    _initted(false),
    _command(command),
    _parms(parms),
    _exit_value(0)
  {
  }

  virtual ~text_grabber() {}

  IMPLEMENT_CLASS_NAME("text_grabber");

  inline int process_exit_value() const { return _exit_value; }

  // performs the last scraping of any output the child process had.
  void finalize() {
    byte_array bytes_found;
    outcome ret = stdio_redirecter::OKAY;
    while (ret == stdio_redirecter::OKAY) {
      fflush(0);
      ret = _stdio.read(bytes_found);
      _file.write(bytes_found);
    }
  }

  void perform_activity(void *ptr) {
    FUNCDEF("perform_activity");

    if (!_initted) {
      _initted = true;
      _stdio.reset(_command, _parms);
      if (_stdio.health() != stdio_redirecter::OKAY) {
        LOG(istring("failed to create the redirecter process: ")
           + common::outcome_name(_stdio.health()));
        exit(140);
      }
    }


    byte_array bytes_found;
    outcome ret = _stdio.read(bytes_found);
    if (ret == stdio_redirecter::OKAY) {
      _file.write(bytes_found);
    } else if ( (ret != stdio_redirecter::OKAY)
        && (ret != stdio_redirecter::NONE_READY) ) {
///      LOG(istring("received error on read: ") + common::outcome_name(ret));
    }

//should we produce stderr any differently?  we do have it trapped.

    ret = _stdio.read_stderr(bytes_found);
    if (ret == stdio_redirecter::OKAY) {
      byte_filer stderr_file(false, stderr);
      stderr_file.write(bytes_found);
    } else if ( (ret != stdio_redirecter::OKAY)
        && (ret != stdio_redirecter::NONE_READY) ) {
///      LOG(istring("received error on read_stderr: ")
///          + common::outcome_name(ret));
    }


/* this stuff needs to be selected with a cmd line argument.  otherwise
   we hang waiting for input when there isn't any.

    // read from standard input.
    byte_filer stdin_reader(false, stdin);
//    while (!stdin_reader.eof()) {

//fix this if we add stdin reading mode.
      const int buff_size = 256 * KILOBYTE;
      bytes_found.reset(buff_size);
      int bytes_read = stdin_reader.read(bytes_found, buff_size);
      if (bytes_read < 0) break;  // oops.
      bytes_found.zap(bytes_read, bytes_found.length() - 1);
      if (bytes_read) {
        int count;
        outcome ret = _stdio.write(bytes_found, count);
        if (ret != stdio_redirecter::OKAY) {
          LOG(istring("received error on write: ") + common::outcome_name(ret));
        }
      }
      portable::sleep_ms(READ_INTERVAL);  // snooze a little.
//    }

*/

    if (!_stdio.running()) {
      // the program shut down, so now we can leave.
      _stdio.close_input();  // close the dead subprocesses input.
      _exit_value = _stdio.exit_value();  // save exit value.
      shutdown_alerter::set_defunct();  // alert the program we're going away.
      this->cancel();  // stop our thread.
      return;
    }

  }

private:
  byte_filer &_file;  // our output file.
  stdio_redirecter _stdio;  // provides our io wrapped application.
  bool _initted;  // tells us whether to crank up the redirecter or not.
  istring _command;  // program we will execute.
  istring _parms;  // parameters for program.
  int _exit_value;  // returned from subprocess.
};

////////////////////////////////////////////////////////////////////////////

class my_anchor : public shutdown_alerter
{
public:
  my_anchor(text_grabber &grabber) : _grabber(grabber) {}
  virtual void handle_startup() {}
  virtual void handle_shutdown() {
    _grabber.stop();
    _grabber.finalize();
    if (saw_interrupt()) {
      // regenerate a ctrl-c to cause the caller to exit also.
BASE_LOG("exiting badly due to interrupt.");
      exit(SIGINT);
    }

if (_grabber.process_exit_value()) {
BASE_LOG("seeing other way to exit badly--subshell.");
}

    exit(_grabber.process_exit_value());
  }

private:
  text_grabber &_grabber;
};

////////////////////////////////////////////////////////////////////////////

#undef static_class_name
#define static_class_name() "io_to_file"

int print_instructions()
{
  BASE_LOG(isprintf("\
This takes two or more arguments and runs a program with redirected I/O.\n\
    %s {outfile} {progname} {arg1} {arg2} ...\n\
The \"outfile\" will be used as a file where the \"progname\" sends its\n\
standard output.  The \"progname\" application will be passed \"arg1\" and\n\
other command line parameters if they are present.\n",
      filename(portable::application_name()).basename().raw().s() ) );
  return 123;
}

int main(int argc, char *argv[])
{
  FUNCDEF("main");
  string_array args;
  for (int q = 1; q < argc; q++)
    args += argv[q];
  if (args.length() < 2)
    return print_instructions();

  // peel off our output file and the command.
  istring outfile = args[0];
  istring command = args[1];
  args.zap(0, 1);
  istring parms = args.text_format(" ", "");
    // turn into a spaced list.

  byte_filer real_output(outfile, "wb");
    // this is where our subprogram's output is written.

  text_grabber io_grabber(real_output, command, parms);
    // create our main thread that runs the show.
  my_anchor anchor(io_grabber);  // the program shutdown manager.
  io_grabber.start(NIL);

  shutdown_alerter::launch_console(anchor,
      filename(portable::application_name()).basename(), 0);

  // at this point the app should be gone and we're exiting.

  io_grabber.stop();  // gone already anyhow.

  io_grabber.finalize();

  return 0;
}

#undef static_class_name

