//####COPYRIGHTBEGIN####
//                                                                          
// ----------------------------------------------------------------------------
// Copyright (C) 1998, 1999, 2000 Red Hat, Inc.
//
// This program is part of the eCos host tools.
//
// 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 program 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
// this program; if not, write to the Free Software Foundation, Inc., 
// 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
//
// ----------------------------------------------------------------------------
//                                                                          
//####COPYRIGHTEND####
//=================================================================
//
//        ser_filter.cxx
//
//        Serial test filter
//
//=================================================================
//=================================================================
//#####DESCRIPTIONBEGIN####
//
// Author(s):     jskov
// Contributors:  jskov
// Date:          1999-03-01
// Description:   This program acts as a filter between GDB and the test
//                running on the target, allowing testing of the serial
//                driver without confusing GDB.
// Usage:         Run the program with one argument, the serial port
//                on with the target is connected.
//                Run serial test in GDB, connecting to the target with
//                'target remote localhost:5678'.
//
// To Do:
//  o Add timeout setup and handling for recovery, can rely on testing
//    agent to control global timeout.
//  o Saving chunks that caused transfer failure?
//     - In SEND with echo, do CRC on 32-byte sub-packets
//  o Additional To Do items under each sub-protocol function.
//  o Option to get all serial IO written (in hex, > and < prepends 
//    input/output lines) to a file.
//  o Clean up the mess in this file....
//  o Make main() listen on two ports - use one for ser filter, the other for
//    null filter connections.
//  o Maybe use environment variable to find X10 reset command.
//####DESCRIPTIONEND####

#include "eCosTestSerialFilter.h"
#include "eCosTestDownloadFilter.h"
#include "eCosTestMonitorFilter.h"
#include "eCosTestUtils.h"
#include "eCosThreadUtils.h"
#include "eCosTrace.h"

bool opt_ser_debug = false;
bool opt_null_filter = false;
bool opt_console_output = false;
bool opt_X10_reset = false;
bool opt_filter_trace = false;
char opt_X10_port[2];
bool opt_monitor = false;

void
no_gdb(const char* pszPort, int nBaud, 
       CeCosSocket::FilterFunc *pSerialToSocketFilterFunc,
       void *pParam, bool *pbStop)
{
  fprintf(stderr, "no_gdb, listening on %s\n",pszPort);
  
  CeCosSocket dummy_socket;
  CeCosSerial serial;
  serial.SetBlockingReads(false);
  bool rc=false;
  
  // Open serial device.
  if (!serial.Open(pszPort,nBaud)){
    ERROR("Couldn't open port %s\n",pszPort);
  } else {
    // Flush the serial buffer.
    serial.Flush();
    
    serial.ClearError();
    enum {BUFSIZE=8192};
    void *pBuf=malloc(BUFSIZE);
    rc=true;
    while(rc && (0==pbStop || !(*pbStop))){
      unsigned int nRead=0;
      
      for(;;) {
        if(serial.Read(pBuf,BUFSIZE,nRead)){
          if(nRead>0){
            break;
          }
          CeCosThreadUtils::Sleep(1);
        } else {
          fprintf(stderr, "Serial read failed (%d)\n", errno);
        }
      }
      
      if(pSerialToSocketFilterFunc){
        rc=pSerialToSocketFilterFunc(pBuf,nRead,serial,dummy_socket,
          pParam);
      }
    }
    free(pBuf);
  }
}

int
main(int argc, char** argv)
{
  int nSock = 0;
  int baud_rate, nTcpPort;
  
  bool opt_no_gdb = false;
  char* ser_port;
  int i=1;
  if(!CeCosTestUtils::CommandLine(argc,argv)){
    goto Usage;
  }
  
  while(i<argc){
    if(argv[i][0]=='-'){
      switch(argv[i][1]){
      case 't':
        // redundant - -v does this
        CeCosTrace ::EnableTracing(CeCosTrace::TRACE_LEVEL_TRACE);
        break;
      case 'f':
        opt_filter_trace = true;
        break;
      case 'S':
        opt_ser_debug = true;
        break;
      case 'm':
        opt_monitor = true;
        break;
      case 'n':
        opt_no_gdb = true;
        // fall through! Output on console when no GDB.
      case 'c':
        opt_console_output = true;
        break;
      case '0':
        opt_null_filter = true;
        break;
      case 'X':
        // X-10 port to reset when connection drops
        opt_X10_reset = true;
        opt_X10_port[0] = argv[i][2];
        opt_X10_port[1] = argv[i][3];
        break;
      default:
        fprintf(stderr,"Unrecognized switch %s\n",argv[i]);
        goto Usage;
        break;
      }
      for(int j=i;j<argc;j++){
        argv[j]=argv[j+1];
      }
      argc--;
      argv[argc]=0;
    } else {
      i++;
    }
  }
  
  if(!((3==argc && opt_no_gdb) || (4==argc && !opt_no_gdb)))
  {
    goto Usage;
  }
  
  if (opt_no_gdb) {
    ser_port = argv[1];
    baud_rate=atoi(argv[2]);
  } else {
    nTcpPort=atoi(argv[1]);
    if(0==nTcpPort){
      fprintf(stderr,"Invalid port %s\n",argv[1]);
      return main(0,argv); // Provoke usage message
    }
    
    ser_port = argv[2];
    baud_rate=atoi(argv[3]);
    
    nSock = CeCosSocket::Listen(nTcpPort);
    if (-1 == nSock) {
      fprintf(stderr, "Couldn't access socket.\n");
      throw "listen failed";
    }
  }
  
  
  if (opt_monitor) {
      fprintf(stdout, "Monitor mode - will not interact with data streams...\n");

      for(;;) {
      
          CeCosTestMonitorFilter* host_filter = 
              new CeCosTestMonitorFilter();
          CeCosTestMonitorFilter* target_filter = 
              new CeCosTestMonitorFilter();

          // Set filter directions
          host_filter->SetOrigin(CeCosTestMonitorFilter::MF_HOST);
          target_filter->SetOrigin(CeCosTestMonitorFilter::MF_TARGET);
          
          // Enable filters
          host_filter->SetVerbose(true);
          target_filter->SetVerbose(true);

          // Set filter functions
          CeCosSocket::FilterFunc *host_filter_function = 
              &SerialMonitorFunction;
          CeCosSocket::FilterFunc *target_filter_function = 
              &SerialMonitorFunction;
          
          try {
              CeCosSocket::ConnectSocketToSerial(nSock, ser_port, 
                                                     baud_rate, 
                                                     target_filter_function, 
                                                     (void*)target_filter, 
                                                     host_filter_function, 
                                                     (void*)host_filter,
                                                     NULL);
          } 
          catch (const char* p) {
              fprintf(stderr, "Caught filter crash: %s\n", p);
          }
          
          delete target_filter;
          delete host_filter;
      }
  }
  
  for(;;) {
    CeCosTestSerialFilter* serial_filter = 
      new CeCosTestSerialFilter();
    CeCosTestDownloadFilter* download_filter = 
      new CeCosTestDownloadFilter();
    
    // Set filter configuration
    serial_filter->SetFilterTrace(opt_filter_trace);
    serial_filter->SetSerialDebug(opt_ser_debug);
    serial_filter->SetConsoleOutput(opt_console_output);
    
    // Set download filter configuration
    download_filter->SetFilterTrace(opt_filter_trace);
    download_filter->SetSerialDebug(opt_ser_debug);
    
    // Set serial side filter
    CeCosSocket::FilterFunc *ser_filter_function = 
      &SerialFilterFunction;
    if (opt_null_filter)
      ser_filter_function = NULL;
    
    // Set socket side filter
    CeCosSocket::FilterFunc *sock_filter_function =
      &DownloadFilterFunction;
    
    try {
      if (opt_no_gdb)
        no_gdb(ser_port, baud_rate, ser_filter_function, 
        (void*)serial_filter, NULL);
      else
        CeCosSocket::ConnectSocketToSerial(nSock, ser_port, 
        baud_rate, 
        ser_filter_function, 
        (void*)serial_filter, 
        sock_filter_function, 
        (void*)download_filter,
        NULL);
    } 
    catch (const char* p) {
      fprintf(stderr, "Caught filter crash: %s\n", p);
    }
    
    if (opt_X10_reset) {
      char X10_cmd[80];
      sprintf(X10_cmd, "/usr/unsupported/bin/x10cli x10 5000 %c %c", 
        opt_X10_port[0], opt_X10_port[1]);
      system(X10_cmd);
    }
    
    delete serial_filter;
    delete download_filter;
  }
  
  return 0;
  
Usage:
  const char *pszMe="ser_filter";
  fprintf(stderr,"Usage: %s [-c -S -0 -Xab] TcpIPport SerialPort BaudRate\n"
    " or:   %s -n [-c -S -0 -Xab] SerialPort BaudRate\n"
    " Switches:\n"
    " -f: Enable filter output tracing.\n"
    " -S: Output data read from serial line.\n"
    " -c: Output data on console instead of via GDB.\n"
    " -m: Work only as a monitor filter. Implies -c.\n"
    " -n: No GDB.\n"
    " -0: Use null filter.\n"
    " -Xab: Reset X-10 Port 'a b' when TCP connection breaks\n",pszMe,pszMe);
  CeCosTestUtils::UsageMessage();
   
  return 1;
}
