//
// 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/MemChecker.hh"
#include "ldastoolsal/FileType.hh"
#include "ldastoolsal/regex.hh"
#include "ldastoolsal/regexmatch.hh"
#include "ldastoolsal/TaskThread.hh"
#include "ldastoolsal/Timeout.hh"
#include "ldastoolsal/unittest.h"

LDASTools::Testing::UnitTest Test;

using LDASTools::AL::FileType;
using LDASTools::AL::SignalHandler;
using LDASTools::AL::Task;
using LDASTools::AL::Thread;

typedef SignalHandler::signal_type signal_type;

namespace
{
#if 0
  static char* SAMFS_NON_STAGEABLE_FILE
    = "/archive/frames/E7/L0/LHO/H-R-6935/H-R-693591696-16.gwf";
#endif /* 0 */

    signal_type init_interupt( );

    class timed_filetype : public Task
    {
    public:
        timed_filetype( const std::string& File )
            : Task(
                  "TimedFileType", Thread::CANCEL_BY_SIGNAL, init_interupt( ) ),
              m_file( File ), m_file_type( "" )
        {
        }

        virtual void
        operator( )( )
        {
            FileType ft( m_file );
            m_file_type = ft.GetFileType( );
        }

    private:
        const std::string m_file;
        std::string       m_file_type;
    };

    static class Wakeup_ : public SignalHandler::Callback
    {
    public:
        virtual void
        SignalCallback( signal_type Signal )
        {
        }
    } wakeup;

    signal_type
    init_interupt( )
    {
        static const signal_type interupt = SignalHandler::SIGNAL_TERMINATE;

        SignalHandler::Register( &wakeup, interupt );
        return interupt;
    }

    inline std::string
    prefix_srcdir( const std::string& Filename )
    {
        std::ostringstream path;

        path << getenv( "SRCDIR" ) << "/" << Filename;
        return path.str( );
    }

    void
    test( const std::string& PathedFilename, const std::string& Type = "" )
    {
        std::ostringstream pattern;

        pattern << "^" << Type << "$";

#if 0 || 1
    FileType	ft( PathedFilename );
#else /* 0 */
        timed_filetype ft( PathedFilename );
        int            status;

        Timeout( &ft, 10, status );
#endif /* 0 */
        Regex t( pattern.str( ).c_str( ), REG_EXTENDED );

        RegexMatch compare( 1 );
        Test.Check( compare.match( t, ft.GetFileType( ).c_str( ) ) )
            << "Checking file type for file: " << PathedFilename
            << " (requested: " << Type << " actual: " << ft.GetFileType( )
            << ")" << std::endl;
    }

    void
    test_simulate( FileType::types Case )
    {
        //-------------------------------------------------------------------
        // Generate a simulated file
        //-------------------------------------------------------------------
        std::string filename;
        const char* filebuffer;
        const char* filepattern;
        int         filebufferlen;

        switch ( Case )
        {
        case FileType::FT_EPS:
            filename = "test.eps";
            filebuffer = "%!PS-Adobe-3.0\nEPSF-";
            filebufferlen = strlen( filebuffer );
            filepattern = "text ps eps";
            break;
        case FileType::FT_FRAME:
            filename = "test.gwf";
            filebuffer = "IGWD\xff";
            filebufferlen = strlen( filebuffer );
            filepattern = "binary frame";
            break;
        case FileType::FT_GIF:
            filename = "test.gif";
            filebuffer = "GIF\xff";
            filebufferlen = strlen( filebuffer );
            filepattern = "binary gif";
            break;
        case FileType::FT_GZIP:
            filename = "test.gz";
            filebuffer = "\x1f\x8b";
            filebufferlen = strlen( filebuffer );
            filepattern = "binary gzip";
            break;
        case FileType::FT_HTML:
            filename = "test.html";
            filebuffer = "<html>";
            filebufferlen = strlen( filebuffer );
            filepattern = "text html";
            break;
        case FileType::FT_HTML_404:
            filename = "test.html";
            filebuffer = "<html> 404 not found";
            filebufferlen = strlen( filebuffer );
            filepattern = "text html 404";
            break;
        case FileType::FT_HTML_ERROR:
            filename = "test.html";
            filebuffer = "<html> Error (456)";
            filebufferlen = strlen( filebuffer );
            filepattern = "text html Error \\(456\\)";
            break;
        case FileType::FT_ILWD:
            filename = "test.ilwd";
            filebuffer = "<?ilwd?>";
            filebufferlen = strlen( filebuffer );
            filepattern = "text ilwd";
            break;
        case FileType::FT_ILWD_FRAME:
            filename = "test.ilwd";
            filebuffer = "<?ilwd?>\nLIGO::Frame";
            filebufferlen = strlen( filebuffer );
            filepattern = "text ilwd ligo frame";
            break;
        case FileType::FT_JPEG:
            filename = "test.jpg";
            filebuffer = "\xFF\xD8\xFF\xE0\x00\x10JFIF";
            filebufferlen = 11;
            filepattern = "binary jpeg";
            break;
        case FileType::FT_PDF:
            filename = "test.pdf";
            filebuffer = "%!PDF-Adobe-3.0\nEPSF-\x84";
            filebufferlen = strlen( filebuffer );
            filepattern = "binary pdf";
            break;
        case FileType::FT_PGP:
            filename = "test.pgp";
            filebuffer = "Hello\nBEGIN PGP MESSAGE\n";
            filebufferlen = strlen( filebuffer );
            filepattern = "text pgp";
            break;
        case FileType::FT_PNG:
            filename = "test.png";
            filebuffer = "\x89PNG";
            filebufferlen = strlen( filebuffer );
            filepattern = "binary png";
            break;
        case FileType::FT_PS:
            filename = "test.ps";
            filebuffer = "%!PS-Adobe-3.0\n";
            filebufferlen = strlen( filebuffer );
            filepattern = "text ps";
            break;
        case FileType::FT_TIFF:
            filename = "test.tif";
            filebuffer = "MM\x00*J\x87\xFF";
            filebufferlen = 7;
            filepattern = "binary tiff";
            break;
        case FileType::FT_XML:
            filename = "test.xml";
            filebuffer = "<?xml stuff>";
            filebufferlen = strlen( filebuffer );
            filepattern = "text xml";
            break;
        case FileType::FT_XML_DOC:
            filename = "test.xml";
            filebuffer = "<?xml stuff><!DOCTYPE stuff>";
            filebufferlen = strlen( filebuffer );
            filepattern = "text xml <!DOCTYPE stuff>";
            break;
        default:
            Test.Check( false )
                << "Unable to create sample for type: "
                << static_cast< unsigned long >( Case ) << std::endl;
            return;
            break;
        }
        //-------------------------------------------------------------------
        // Generate the file
        //-------------------------------------------------------------------
        int fd = open( filename.c_str( ), O_TRUNC | O_WRONLY | O_CREAT, 0700 );
        Test.Check( write( fd, filebuffer, filebufferlen ) == filebufferlen )
            << "Writing of test buffer" << std::endl;
        Test.Check( close( fd ) == 0 ) << "Closing of test file." << std::endl;
        //-------------------------------------------------------------------
        // Verify the match
        //-------------------------------------------------------------------
        test( filename, filepattern );

        //-------------------------------------------------------------------
        // Remove the file
        //-------------------------------------------------------------------
        Test.Check( unlink( filename.c_str( ) ) == 0 )
            << "Removed test file." << 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( prefix_srcdir( "CTestTestfile.cmake" ), "text unknown" );
#if 0
  test( "FileType", "text #! /bin/.*sh" );
  test( "FileType.o", "binary elf" );
  if ( access( SAMFS_NON_STAGEABLE_FILE, R_OK ) == 0 )
  {
    test( SAMFS_NON_STAGEABLE_FILE, "unknown [(] errno=.*[)] [)]" );
  }
#endif /* 0 */

    test_simulate( FileType::FT_EPS );
    test_simulate( FileType::FT_FRAME );
    test_simulate( FileType::FT_GIF );
    test_simulate( FileType::FT_GZIP );
    test_simulate( FileType::FT_HTML );
    test_simulate( FileType::FT_HTML_404 );
    test_simulate( FileType::FT_HTML_ERROR );
    test_simulate( FileType::FT_ILWD );
    test_simulate( FileType::FT_ILWD_FRAME );
    test_simulate( FileType::FT_JPEG );
    test_simulate( FileType::FT_PDF );
    test_simulate( FileType::FT_PGP );
    test_simulate( FileType::FT_PNG );
    test_simulate( FileType::FT_PS );
    test_simulate( FileType::FT_TIFF );
    test_simulate( FileType::FT_XML );
    test_simulate( FileType::FT_XML_DOC );

    Test.Exit( );
    return 1;
}
