//
// LDASTools frameCPP - Frame File Information Analysis Implementation
//
// Copyright (C) 2025 California Institute of Technology
//
// LDASTools frameCPP 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 frameCPP 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.

#include "framecpp/FrameFileInfo.hh"

#include "ldastoolsal/types.hh"
#include "ldastoolsal/Logger.hh"
#include "ldastoolsal/LoggerManager.hh"

#include <iomanip>
#include <algorithm>
#include <cctype>

namespace FrameCPP::OOInterface
{

    // Utility Functions

    bool
    isGwfFile( const std::filesystem::path& filepath )
    {
        auto extension = filepath.extension( ).string( );
        std::transform( extension.begin( ),
                        extension.end( ),
                        extension.begin( ),
                        ::tolower );
        return extension == ".gwf";
    }

    std::string
    getDataTypeSizes( )
    {
        return std::to_string( sizeof( INT_2U ) ) + "," +
            std::to_string( sizeof( INT_4U ) ) + "," +
            std::to_string( sizeof( INT_8U ) ) + "," +
            std::to_string( sizeof( REAL_4 ) ) + "," +
            std::to_string( sizeof( REAL_8 ) );
    }

    // FrameFileAnalyzer Implementation

    FrameFileAnalyzer::FrameFileAnalyzer( const FrameCPP::Common::FrameCPPServices& services )
        : services_( services ),
          logger_( const_cast< FrameCPP::Common::FrameCPPServices& >( services )
                       .getLogger( "framecpp.framefileinfo" ) )
    {
    }

    std::string
    FrameFileAnalyzer::calculateByteOrder( const Universal::FrHeader& header )
    {
        bool isSwapped = header.ByteSwapping( );

        // Determine host endianness (extracted from original framecpp_info.cc)
        union
        {
            uint32_t i;
            char     c[ 4 ];
        } test = { 0x01020304 };
        bool hostIsLittleEndian = ( test.c[ 0 ] == 4 );

        if ( hostIsLittleEndian )
        {
            return isSwapped ? "BIGENDIAN (Swapped)" : "LITTLEENDIAN (Native)";
        }
        else
        {
            return isSwapped ? "LITTLEENDIAN (Swapped)" : "BIGENDIAN (Native)";
        }
    }

    std::optional< FrameInfo >
    FrameFileAnalyzer::readFrameHeader(
        const std::filesystem::path& filepath ) const
    {
        try
        {
            // Check if file exists
            if ( !std::filesystem::exists( filepath ) )
            {
                if ( logger_ )
                {
                    logger_log_debug(
                        logger_, 20, "File not found: ", filepath.string( ) );
                }
                return std::nullopt;
            }

            if ( logger_ )
            {
                logger_log_debug(
                    logger_,
                    30,
                    "Using SequentialFrameReader for robust frame validation: ",
                    filepath.string( ) );
            }

            // Use SequentialFrameReader for robust frame validation
            FrameCPP::Common::SequentialFrameReader reader( filepath.string( ),
                                                            services_ );

            // Get the file header (FrHeader) - SequentialFrameReader automatically
            // validates the frame structure
            // Use GetFrHeader() which is public and returns a reference
            try 
            {
                const Common::FrHeader& fr_header_ref = reader.GetFrHeader();
                
                // Cast from Common::FrHeader to Universal::FrHeader since OOInterface can depend on Universal types
                // We need to do this while the reader is still alive
                const Universal::FrHeader* universal_header = dynamic_cast<const Universal::FrHeader*>(&fr_header_ref);
                if ( !universal_header )
                {
                    if ( logger_ )
                    {
                        logger_log_debug( logger_,
                                          20,
                                          "Failed to cast Common::FrHeader to Universal::FrHeader for: ",
                                          filepath.string( ) );
                    }
                    return std::nullopt;
                }

                if ( logger_ )
                {
                    logger_log_debug(
                        logger_,
                        30,
                        "Successfully read Universal::FrHeader from: ",
                        filepath.string( ),
                        " - version: ",
                        universal_header->GetVersion( ) );
                }

                // Extract the frame info while the reader is still alive
                FrameInfo info = extractFrameInfo(*universal_header);
                
                if ( logger_ )
                {
                    logger_log_debug( logger_,
                                      30,
                                      "Extracted frame info for: ",
                                      filepath.string( ),
                                      " - version: ",
                                      info.frame_spec_version,
                                      " - originator: ",
                                      info.originator );
                }
                
                return info;
            }
            catch ( const std::exception& e )
            {
                if ( logger_ )
                {
                    logger_log_debug( logger_,
                                      20,
                                      "SequentialFrameReader could not extract "
                                      "valid frame header from: ",
                                      filepath.string( ),
                                      " - error: ",
                                      e.what( ) );
                }
                return std::nullopt;
            }
        }
        catch ( const std::exception& e )
        {
            if ( logger_ )
            {
                logger_log_debug( logger_,
                                  10,
                                  "Exception reading frame header from ",
                                  filepath.string( ),
                                  ": ",
                                  e.what( ) );
            }
            return std::nullopt;
        }
    }

    FrameInfo
    FrameFileAnalyzer::extractFrameInfo( const Universal::FrHeader& header )
    {
        FrameInfo info;
        info.is_valid = true;

        // Extract fields (following original framecpp_info.cc logic)
        info.originator = std::string( header.GetOriginator( ) );
        info.frame_spec_version = header.GetVersion( );
        info.library_revision = header.GetLibraryRevision( );
        info.library_name = header.GetFrameLibraryName( );
        info.endianness = calculateByteOrder( header );
        info.data_type_sizes = getDataTypeSizes( );

        // Checksum scheme with version-dependent message (from original logic)
        info.checksum_scheme = ( header.GetVersion( ) >= 8 )
            ? "[Not available in lightweight read]"
            : "[Not available before v8]";

        return info;
    }

    std::optional< FrameInfo >
    FrameFileAnalyzer::analyzeFile(
        const std::filesystem::path& filepath ) const
    {
        if ( logger_ )
        {
            logger_log_debug(
                logger_, 20, "Analyzing file: ", filepath.string( ) );
        }

        // readFrameHeader now returns the FrameInfo directly (or nullopt on error)
        auto info = readFrameHeader( filepath );
        
        if ( info && logger_ )
        {
            logger_log_debug( logger_,
                              30,
                              "Analysis complete for: ",
                              filepath.string( ),
                              " - version: ",
                              info->frame_spec_version,
                              " - originator: ",
                              info->originator );
        }

        return info;
    }

    std::vector<
        std::pair< std::filesystem::path, std::optional< FrameInfo > > >
    FrameFileAnalyzer::analyzeFiles(
        const std::vector< std::filesystem::path >& filepaths ) const
    {
        std::vector<
            std::pair< std::filesystem::path, std::optional< FrameInfo > > >
            results;
        results.reserve( filepaths.size( ) );

        if ( logger_ )
        {
            logger_log_debug( logger_,
                              20,
                              "Starting batch analysis of ",
                              filepaths.size( ),
                              " files" );
        }

        for ( const auto& filepath : filepaths )
        {
            auto info = analyzeFile( filepath );
            results.emplace_back( filepath, std::move( info ) );
        }

        if ( logger_ )
        {
            auto success_count = std::count_if(
                results.begin( ), results.end( ), []( const auto& pair ) {
                    return pair.second.has_value( );
                } );
            logger_log_debug( logger_,
                              20,
                              "Batch analysis complete: ",
                              success_count,
                              "/",
                              results.size( ),
                              " successful" );
        }

        return results;
    }

    bool
    FrameFileAnalyzer::isValidFrameFile( const std::filesystem::path& filepath )
    {
        try
        {
            // Quick validation - check extension and file existence
            if ( !isGwfFile( filepath ) ||
                 !std::filesystem::exists( filepath ) )
            {
                return false;
            }

            // Use SequentialFrameReader for robust validation
            try
            {
                // Create minimal services for validation (need LoggerManager)
                LDASTools::AL::LoggerManager logger_manager;
                FrameCPP::Common::FrameCPPServices services(logger_manager);
                FrameCPP::Common::SequentialFrameReader reader(
                    filepath.string( ), services );

                // If SequentialFrameReader can get a valid header, the file is
                // valid
                auto frame_header = reader.getFrameHeader( );
                return frame_header != nullptr;
            }
            catch ( const std::exception& )
            {
                return false;
            }
        }
        catch ( const std::exception& )
        {
            return false;
        }
    }

    // FrameInfoFormatter Implementation

    FrameInfoFormatter::FrameInfoFormatter( const DisplayConfig& config,
                                            std::ostream&        output )
        : config_( config ), output_( output )
    {
    }

    void
    FrameInfoFormatter::formatField(
        std::string_view             label,
        std::string_view             value,
        FrameField                   field,
        const std::filesystem::path& filepath ) const
    {
        if ( !config_.shouldDisplay( field ) )
        {
            return;
        }

        if ( config_.mode == DisplayConfig::Mode::Verbose )
        {
            output_ << std::setw( 20 ) << std::left << label << ": " << value
                    << std::endl;
        }
        else
        { // Terse mode
            if ( config_.show_filename )
            {
                output_ << filepath.string( ) << ":" << value << std::endl;
            }
            else
            {
                output_ << value << std::endl;
            }
        }
    }

    void
    FrameInfoFormatter::formatDataSizes(
        const FrameInfo& info, const std::filesystem::path& filepath ) const
    {
        if ( !config_.shouldDisplay( FrameField::DataSizes ) )
        {
            return;
        }

        if ( config_.mode == DisplayConfig::Mode::Verbose )
        {
            output_ << std::setw( 20 ) << std::left << "Data Type Sizes"
                    << ":" << std::endl;
            // Detailed breakdown for verbose mode (from original
            // framecpp_info.cc)
            output_ << "  INT_2U: " << sizeof( INT_2U ) << " bytes"
                    << std::endl;
            output_ << "  INT_4U: " << sizeof( INT_4U ) << " bytes"
                    << std::endl;
            output_ << "  INT_8U: " << sizeof( INT_8U ) << " bytes"
                    << std::endl;
            output_ << "  REAL_4: " << sizeof( REAL_4 ) << " bytes"
                    << std::endl;
            output_ << "  REAL_8: " << sizeof( REAL_8 ) << " bytes"
                    << std::endl;
        }
        else
        { // Terse mode
            if ( config_.show_filename )
            {
                output_ << filepath.string( ) << ":" << info.data_type_sizes
                        << std::endl;
            }
            else
            {
                output_ << info.data_type_sizes << std::endl;
            }
        }
    }

    void
    FrameInfoFormatter::formatVerboseHeader(
        const std::filesystem::path& filepath ) const
    {
        if ( config_.mode == DisplayConfig::Mode::Verbose )
        {
            output_ << "=== Frame Header Information ===" << std::endl;
            output_ << "File: " << filepath.string( ) << std::endl;
            output_ << std::endl;
        }
    }

    void
    FrameInfoFormatter::formatVerboseFooter( ) const
    {
        if ( config_.mode == DisplayConfig::Mode::Verbose )
        {
            output_ << std::endl;
        }
    }

    void
    FrameInfoFormatter::format( const FrameInfo&             info,
                                const std::filesystem::path& filepath ) const
    {
        formatVerboseHeader( filepath );

        // Format all fields using the helper method
        formatField(
            "Originator", info.originator, FrameField::Originator, filepath );
        formatField( "Frame Spec Version",
                     std::to_string( info.frame_spec_version ),
                     FrameField::FrameSpecVersion,
                     filepath );
        formatField( "Library Revision",
                     std::to_string( info.library_revision ),
                     FrameField::LibraryRevision,
                     filepath );
        formatField( "Library Name",
                     info.library_name,
                     FrameField::LibraryName,
                     filepath );
        formatField( "Checksum Scheme",
                     info.checksum_scheme,
                     FrameField::ChecksumScheme,
                     filepath );
        formatField(
            "Byte Order", info.endianness, FrameField::Endianness, filepath );

        // Data sizes need special formatting
        formatDataSizes( info, filepath );

        formatVerboseFooter( );
    }

    void
    FrameInfoFormatter::formatBatch(
        const std::vector<
            std::pair< std::filesystem::path, std::optional< FrameInfo > > >&
            results ) const
    {
        for ( const auto& [ filepath, info_opt ] : results )
        {
            if ( info_opt )
            {
                format( *info_opt, filepath );
            }
            else
            {
                formatError( filepath, "Analysis failed" );
            }
        }
    }

    void
    FrameInfoFormatter::formatError( const std::filesystem::path& filepath,
                                     std::string_view error_message ) const
    {
        if ( config_.mode == DisplayConfig::Mode::Verbose )
        {
            output_ << "=== Error ===" << std::endl;
            output_ << "File: " << filepath.string( ) << std::endl;
            output_ << "Error: " << error_message << std::endl;
            output_ << std::endl;
        }
        else
        { // Terse mode
            if ( config_.show_filename )
            {
                output_ << filepath.string( ) << ":ERROR:" << error_message
                        << std::endl;
            }
            else
            {
                output_ << "ERROR:" << error_message << std::endl;
            }
        }
    }

} // namespace FrameCPP::OOInterface
