//
// LDASTools AL - A library collection to provide an abstraction layer
//
// Copyright (C) 2018 California Institute of Technology
//
// LDASTools AL is free software; you may 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 (GPLv2) of the
// License or at your discretion, any later version.
//
// LDASTools AL is distributed in the hope that it will be useful, but
// without any warranty or even the implied warranty of merchantability
// or fitness for a particular purpose. See the GNU General Public
// License (GPLv2) for more details.
//
// Neither the names of the California Institute of Technology (Caltech),
// The Massachusetts Institute of Technology (M.I.T), The Laser
// Interferometer Gravitational-Wave Observatory (LIGO), nor the names
// of its contributors may be used to endorse or promote products derived
// from this software without specific prior written permission.
//
// You should have received a copy of the licensing terms for this
// software included in the file LICENSE located in the top-level
// directory of this package. If you did not, you can view a copy at
// http://dcc.ligo.org/M1500244/LICENSE
//

#include <fcntl.h>
#include <strings.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

#include <sstream>

#include "ldastoolsal/CommandLineOptions.hh"
#include "ldastoolsal/MemChecker.hh"
#include "ldastoolsal/unittest.h"

typedef LDASTools::AL::CommandLineOptions CommandLineOptions;
typedef CommandLineOptions::Option        Option;
typedef CommandLineOptions::OptionSet     OptionSet;

LDASTools::Testing::UnitTest Test;

namespace
{
    class CommandLine : protected CommandLineOptions
    {
    public:
        using CommandLineOptions::empty;
        using CommandLineOptions::Pop;
        typedef CommandLineOptions::option_type option_type;

        bool no_args_seen;
        bool opt_args_seen;
        int  opt_args_value;
        bool req_args_seen;
        int  req_args_value;

        CommandLine( int ArgC, const char** ArgV );

#ifdef LCL_UNUSED
        inline void
        Usage( int ExitValue ) const
        {
#if 0
      std::cerr << "Usage: "
		<< ProgramName( )
		<< m_options
		<< std::endl;
      depart( ExitValue );
#endif /* 0 */
        }
#endif // LCL_UNUSED

#ifdef LCL_UNUSED
        inline bool
        BadOption( ) const
        {
            bool retval = false;

            for ( const_iterator cur = begin( ), last = end( ); cur != last;
                  ++cur )
            {
                if ( ( *cur )[ 0 ] == '-' )
                {
                    std::cerr << "ABORT: Bad option: " << *cur << std::endl;
                    retval = true;
                }
            }
            return retval;
        }
#endif // LCL_UNUSED

    private:
        enum option_ypes
        {
            OPT_NO_ARGS,
            OPT_REQ_ARGS,
            OPT_OPT_ARGS
        };

        OptionSet m_options;
    };

    CommandLine::CommandLine( int ArgC, const char** ArgV )
        : CommandLineOptions( ArgC, ArgV ), no_args_seen( false ),
          opt_args_seen( false ), opt_args_value( 0 ), req_args_seen( false ),
          req_args_value( 0 )
    {
        //---------------------------------------------------------------------
        // Setup the options that will be recognized.
        //---------------------------------------------------------------------
        std::ostringstream desc;

        m_options.Synopsis( "[options] <file> [<file> ...]" );

        m_options.Summary(
            "This verifies the contents of each file on the command line." );

        //:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
        m_options.Add(
            Option( OPT_NO_ARGS,
                    "no-arg",
                    Option::ARG_NONE,
                    "test option that does not receive an option" ) );

        //:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
        m_options.Add( Option( OPT_OPT_ARGS,
                               "opt-arg",
                               Option::ARG_OPTIONAL,
                               "test option that may receive an option",
                               "<integer>" ) );

        //:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
        m_options.Add( Option( OPT_REQ_ARGS,
                               "req-arg",
                               Option::ARG_REQUIRED,
                               "test option that requires to receive an option",
                               "<integer>" ) );

#if 0
    //:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
    desc.str( "" );
    desc <<
      "Number of 1k blocks to use for io."
      " (Default: " << BlockSize( ) << " )"
      ;
    m_options.
      Add( Option( OPT_BLOCK_SIZE,
		   "bs",
		   Option::ARG_REQUIRED,
		   desc.str( ),
		   "<block_size>") );
#endif /* 0 */

        //---------------------------------------------------------------------
        // Parse the options specified on the command line
        //---------------------------------------------------------------------

        {
            std::string arg_name;
            std::string arg_value;
            bool        parsing = true;

            while ( parsing )
            {
                const int cmd_id( Parse( m_options, arg_name, arg_value ) );

                switch ( cmd_id )
                {
                case CommandLineOptions::OPT_END_OF_OPTIONS:
                    parsing = false;
                    break;
                case OPT_NO_ARGS:
                {
                    no_args_seen = true;
                }
                break;
                case OPT_OPT_ARGS:
                {
                    opt_args_seen = true;
                    if ( arg_value.size( ) > 0 )
                    {
                        std::istringstream v( arg_value );

                        v >> opt_args_value;
                    }
                }
                break;
                case OPT_REQ_ARGS:
                {
                    req_args_seen = true;
                    std::istringstream v( arg_value );

                    v >> req_args_value;
                }
                break;
                }
            }
        }
    }

    void
    test_no_arg_options( )
    {
        static const char* test_prefix = "NoArg: ";
        static const char* argv[] = { "program",
                                      "--no-arg",
                                      (const char*)NULL };

        static const int argc = ( sizeof( argv ) / sizeof( *argv ) ) - 1;

        CommandLine cl( argc, &( argv[ 0 ] ) );

        Test.Check( cl.no_args_seen )
            << test_prefix
            << "Checking parsing of options that require no arguments"
            << std::endl;
    }

    void
    test_req_arg_options( )
    {
        static const char* test_prefix = "ReqArg: ";
        {
            const char* argv[] = {
                "program", "--req-arg", "75", (const char*)NULL
            };

            int argc = ( sizeof( argv ) / sizeof( *argv ) ) - 1;

            CommandLine cl( argc, argv );

            Test.Check( ( cl.req_args_seen == true ) &&
                        ( cl.req_args_value == 75 ) )
                << test_prefix
                << "Checking parsing of options that require a single arguments"
                << std::endl;
        }
        {
            const char* argv[] = { "program", "--req-arg", (const char*)NULL };

            int argc = ( sizeof( argv ) / sizeof( *argv ) ) - 1;
            int caught_exception = 0;

            try
            {
                CommandLine cl( argc, argv );

                Test.Check( ( cl.req_args_seen == false ) ||
                            ( cl.req_args_value != 75 ) )
                    << test_prefix
                    << "Checking handling of missing required argument"
                    << std::endl;
            }
            catch ( const std::runtime_error& Exception )
            {
                caught_exception = 1;
            }
            catch ( const std::exception& Exception )
            {
                caught_exception = -1;
            }
            std::ostringstream msg;

            Test.Check( caught_exception == 1 )
                << test_prefix << "Properly handle missing argument"
                << std::endl;
        }
        {
            const char* argv[] = {
                "program", "--req-arg", "--no-arg", (const char*)NULL
            };

            int argc = ( sizeof( argv ) / sizeof( *argv ) ) - 1;
            int caught_exception = 0;

            try
            {
                CommandLine cl( argc, argv );
            }
            catch ( const std::runtime_error& Exception )
            {
                caught_exception = 1;
            }
            catch ( const std::exception& Exception )
            {
                caught_exception = -1;
            }
            std::ostringstream msg;

            Test.Check( caught_exception == 1 )
                << test_prefix
                << "Properly handle missing argument followed by argument via "
                   "exception"
                << std::endl;
        }
    }

    void
    test_opt_arg_options( )
    {
        static const char* test_prefix = "OptArg: ";

        {
            const char* argv[] = {
                "program", "--opt-arg", "85", (const char*)NULL
            };

            int argc = ( sizeof( argv ) / sizeof( *argv ) ) - 1;

            CommandLine cl( argc, argv );

            Test.Check( ( cl.opt_args_seen == true ) &&
                        ( cl.opt_args_value == 85 ) )
                << test_prefix
                << "Checking parsing of options that optionally takes a single "
                   "arguments"
                << std::endl;
        }
        {
            const char* argv[] = { "program", "--opt-arg", (const char*)NULL };

            int argc = ( sizeof( argv ) / sizeof( *argv ) ) - 1;
            int caught_exception = 0;

            try
            {
                CommandLine cl( argc, argv );

                Test.Check( ( cl.opt_args_seen == true ) &&
                            ( cl.opt_args_value == 0 ) )
                    << test_prefix << "Checking handling of missing argument"
                    << std::endl;
            }
            catch ( const std::runtime_error& Exception )
            {
                caught_exception = 1;
            }
            catch ( const std::exception& Exception )
            {
                caught_exception = -1;
            }
            std::ostringstream msg;

            Test.Check( caught_exception == 0 )
                << test_prefix << "Properly handle missing argument"
                << std::endl;
        }
        {
            const char* argv[] = {
                "program", "--opt-arg", "--no-arg", (const char*)NULL
            };

            int  argc = ( sizeof( argv ) / sizeof( *argv ) ) - 1;
            int  caught_exception = 0;
            bool correct = false;

            try
            {
                CommandLine cl( argc, argv );

                correct = ( ( cl.opt_args_seen == true ) &&
                            ( cl.opt_args_value == 0 ) &&
                            ( cl.no_args_seen == true ) );
            }
            catch ( const std::runtime_error& Exception )
            {
                caught_exception = 1;
            }
            catch ( const std::exception& Exception )
            {
                caught_exception = -1;
            }
            std::ostringstream msg;

            Test.Check( ( caught_exception == 0 ) && correct )
                << test_prefix
                << "Properly handle missing argument followed by argument via "
                   "exception"
                << std::endl;
        }
    }

} // namespace

int
main( int ArgC, char** ArgV )
{
    //---------------------------------------------------------------------
    // Establish that this program is safe for memory checking
    //---------------------------------------------------------------------
    LDASTools::AL::MemChecker::Trigger trig( true );

    //---------------------------------------------------------------------
    // Initialize the test structure
    //---------------------------------------------------------------------
    Test.Init( ArgC, ArgV );

    //---------------------------------------------------------------------
    // Run through a series of tests
    //---------------------------------------------------------------------

    test_no_arg_options( );
    test_req_arg_options( );
    test_opt_arg_options( );

    //---------------------------------------------------------------------
    // Report findings
    //---------------------------------------------------------------------
    Test.Exit( );
    return 1;
}
