// ATC-Main.cxx -- FGFS interface to ATC hardware
//
// Written by Curtis Olson, started January 2002

// SPDX-FileCopyrightText: (C) 2002  Curtis L. Olson - http://www.flightgear.org/~curt
// SPDX-License-Identifier: GPL-2.0-or-later

#include <config.h>

#include <simgear/compiler.h>

#include <stdlib.h>		// atoi() atof() abs()
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>

#include <stdio.h>              //snprintf
#ifdef _WIN32
#  include <io.h>                 //lseek, read, write
#endif

#include <string>

#include <simgear/debug/logstream.hxx>
#include <simgear/props/props_io.hxx>
#include <simgear/io/iochannel.hxx>
#include <simgear/math/sg_types.hxx>
#include <simgear/misc/sg_path.hxx>
#include <simgear/props/props.hxx>
#include <simgear/structure/exception.hxx>

#include <Scripting/NasalSys.hxx>
#include <Main/fg_props.hxx>
#include <Main/globals.hxx>

#include "ATC-Main.hxx"

using std::string;
using std::cout;
using std::endl;
using std::vector;

// Lock the ATC hardware
static int fgATCMainLock( int fd ) {
    // rewind
    lseek( fd, 0, SEEK_SET );

    char tmp[2];
    int result = read( fd, tmp, 1 );
    if ( result != 1 ) {
	SG_LOG( SG_IO, SG_DEBUG, "Lock failed" );
    }

    return result;
}


// Write a radios command
static int fgATCMainRelease( int fd ) {
    // rewind
    lseek( fd, 0, SEEK_SET );

    char tmp[2];
    tmp[0] = tmp[1] = 0;
    int result = write( fd, tmp, 1 );

    if ( result != 1 ) {
	SG_LOG( SG_IO, SG_DEBUG, "Release failed" );
    }

    return result;
}


void FGATCMain::init_config() {
#if defined( unix ) || defined( __CYGWIN__ )
    // Next check home directory for .fgfsrc.hostname file
    SGPath atcsim_config = SGPath::home();
    atcsim_config.append( ".fgfs-atc610x.xml" );
  	try {
  	  SG_LOG(SG_NETWORK, SG_ALERT,
  		 "Warning: loading deprecated config file: " << atcsim_config);
  	  readProperties( atcsim_config, globals->get_props() );
  	} catch (const sg_exception &e) {
  	  // fail silently, this is an old style config file I want to continue
  	  // to support if it exists.
  	}

#endif
}


// Open and initialize ATC hardware
bool FGATCMain::open() {
    if ( is_enabled() ) {
	SG_LOG( SG_IO, SG_ALERT, "This shouldn't happen, but the channel "
		<< "is already in use, ignoring" );
	return false;
    }

    SG_LOG( SG_IO, SG_ALERT,
	    "Initializing ATC hardware, please wait ..." );

    // This loads the config parameters generated by "simcal"
    init_config();

    /////////////////////////////////////////////////////////////////////
    // Open the /proc files
    /////////////////////////////////////////////////////////////////////

    string lock0_file = "/proc/atcflightsim/board0/lock";
    string lock1_file = "/proc/atcflightsim/board1/lock";

    lock0_fd = ::open( lock0_file.c_str(), O_RDWR );
    if ( lock0_fd == -1 ) {
	SG_LOG( SG_IO, SG_ALERT, "errno = " << errno );
	char msg[256];
	snprintf( msg, 256, "Error opening %s", lock0_file.c_str() );
	perror( msg );
	exit( -1 );
    }

    lock1_fd = ::open( lock1_file.c_str(), O_RDWR );
    if ( lock1_fd == -1 ) {
	SG_LOG( SG_IO, SG_ALERT, "errno = " << errno );
	char msg[256];
	snprintf( msg, 256, "Error opening %s", lock1_file.c_str() );
	perror( msg );
	exit( -1 );
    }

    if ( !input0_path.isNull() ) {
        input0 = new FGATCInput( 0, input0_path );
        input0->open();
    }
    if ( !input1_path.isNull() ) {
        input1 = new FGATCInput( 1, input1_path );
        input1->open();
    }
    if ( !output0_path.isNull() ) {
        output0 = new FGATCOutput( 0, output0_path );
        output0->open( lock0_fd );
    }
    if ( !output1_path.isNull() ) {
        output1 = new FGATCOutput( 1, output1_path );
        output1->open( lock1_fd );
    }

    set_hz( 30 );		// default to processing requests @ 30Hz
    set_enabled( true );

    /////////////////////////////////////////////////////////////////////
    // Finished initing hardware
    /////////////////////////////////////////////////////////////////////

    SG_LOG( SG_IO, SG_ALERT,
	    "Done initializing ATC hardware." );

    return true;
}


bool FGATCMain::process() {
    // cout << "Main::process()\n";

    bool board0_locked = false;
    bool board1_locked = false;

    if ( input0 != NULL || output0 != NULL ) {
        // Lock board0 if we have a configuration for it
        if ( fgATCMainLock( lock0_fd ) > 0 ) {
            board0_locked = true;
        }
    }

    if ( input1 != NULL || output1 != NULL ) {
        // Lock board1 if we have a configuration for it
        if ( fgATCMainLock( lock1_fd ) > 0 ) {
            board1_locked = true;
        }
    }

    // cout << "  locks: ";
    // if ( board0_locked ) { cout << "board0 "; }
    // if ( board1_locked ) { cout << "board1 "; }
    // cout << endl;

    // process the ATC inputs
    if ( input0 != NULL && board0_locked ) {
        input0->process();
    }
    if ( input1 != NULL && board1_locked ) {
        input1->process();
    }

    // run our custom nasal script.  This is a layer above the raw
    // hardware inputs.  It handles situations where there isn't a
    // direct 1-1 linear mapping between ATC functionality and FG
    // functionality, and handles situations where FG expects more
    // functionality from the interface than the ATC hardware can
    // directly provide.

    auto n = globals->get_subsystem<FGNasalSys>();
    bool result = n->parseAndRun( "atcsim.update()" );
    if ( !result ) {
        SG_LOG( SG_NETWORK, SG_ALERT, "Nasal: atcsim.update() failed!" );
    }

    // process the ATC outputs
    if ( output0 != NULL && board0_locked ) {
        output0->process();
    }
    if ( output1 != NULL && board1_locked ) {
        output1->process();
    }

    if ( board0_locked ) {
        fgATCMainRelease( lock0_fd );
    }
    if ( board1_locked ) {
        fgATCMainRelease( lock1_fd );
    }

    return true;
}


bool FGATCMain::close() {
    cout << "FGATCMain::close()" << endl;

    int result;

    if ( input0 != NULL ) {
        input0->close();
    }
    if ( input1 != NULL ) {
        input1->close();
    }
    if ( output0 != NULL ) {
        output0->close();
    }
    if ( output1 != NULL ) {
        output1->close();
    }

    result = ::close( lock0_fd );
    if ( result == -1 ) {
	SG_LOG( SG_IO, SG_ALERT, "errno = " << errno );
	char msg[256];
	snprintf( msg, 256, "Error closing lock0_fd" );
	perror( msg );
	exit( -1 );
    }

    result = ::close( lock1_fd );
    if ( result == -1 ) {
	SG_LOG( SG_IO, SG_ALERT, "errno = " << errno );
	char msg[256];
	snprintf( msg, 256, "Error closing lock1_fd" );
	perror( msg );
	exit( -1 );
    }

    return true;
}
