// ObjectNode.h - a node that builds a single object from a source file
//------------------------------------------------------------------------------
#pragma once

// Includes
//------------------------------------------------------------------------------
#include "FileNode.h"

// Core
#include "Core/Containers/Array.h"
#include "Core/Containers/UniquePtr.h"
#include "Core/Env/Assert.h"
#include "Core/Process/Atomic.h"
#include "Core/Process/Process.h"

// Forward Declarations
//------------------------------------------------------------------------------
class Args;
class CompilerDriverBase;
class ConstMemoryStream;
class Function;
class MultiBuffer;
class NodeGraph;
class NodeProxy;
class ObjectListNode;
class ObjectNode;
enum class ArgsResponseFileMode : uint32_t;

// Defines
//------------------------------------------------------------------------------
#if defined( DEBUG )
    // Enabled support for some test functionality
    #define ENABLE_FAKE_SYSTEM_FAILURE
#endif

// ObjectNode
//------------------------------------------------------------------------------
class ObjectNode : public FileNode
{
    REFLECT_NODE_DECLARE( ObjectNode )
public:
    ObjectNode();
    virtual bool Initialize( NodeGraph & nodeGraph, const BFFToken * iter, const Function * function ) override;
    virtual ~ObjectNode() override;

    static Node::Type GetTypeS() { return Node::OBJECT_NODE; }

    class CompilerFlags
    {
    public:
        bool IsCacheable() const { return ( ( m_Flags & FLAG_CAN_BE_CACHED ) != 0 ); }
        bool IsDistributable() const { return ( ( m_Flags & FLAG_CAN_BE_DISTRIBUTED ) != 0 ); }
        bool IsUsingPCH() const { return ( ( m_Flags & FLAG_USING_PCH ) != 0 ); }
        bool IsGCC() const { return ( ( m_Flags & FLAG_GCC ) != 0 ); }
        bool IsMSVC() const { return ( ( m_Flags & FLAG_MSVC ) != 0 ); }
        bool IsCreatingPCH() const { return ( ( m_Flags & FLAG_CREATING_PCH ) != 0 ); }
        bool IsSNC() const { return ( ( m_Flags & FLAG_SNC ) != 0 ); }
        bool IsUsingCLR() const { return ( ( m_Flags & FLAG_USING_CLR ) != 0 ); }
        bool IsClang() const { return ( ( m_Flags & FLAG_CLANG ) != 0 ); }
        bool IsUnity() const { return ( ( m_Flags & FLAG_UNITY ) != 0 ); }
        bool IsIsolatedFromUnity() const { return ( ( m_Flags & FLAG_ISOLATED_FROM_UNITY ) != 0 ); }
        bool IsUsingPDB() const { return ( ( m_Flags & FLAG_USING_PDB ) != 0 ); }
        bool IsCodeWarriorWii() const { return ( ( m_Flags & CODEWARRIOR_WII ) != 0 ); }
        bool IsGreenHillsWiiU() const { return ( ( m_Flags & GREENHILLS_WIIU ) != 0 ); }
        bool IsCUDANVCC() const { return ( ( m_Flags & FLAG_CUDA_NVCC ) != 0 ); }
        bool IsIncludesInStdErr() const { return ( ( m_Flags & FLAG_INCLUDES_IN_STDERR ) != 0 ); }
        bool IsQtRCC() const { return ( ( m_Flags & FLAG_QT_RCC ) != 0 ); }
        bool IsWarningsAsErrorsMSVC() const { return ( ( m_Flags & FLAG_WARNINGS_AS_ERRORS_MSVC ) != 0 ); }
        bool IsVBCC() const { return ( ( m_Flags & FLAG_VBCC ) != 0 ); }
        bool IsUsingStaticAnalysisMSVC() const { return ( ( m_Flags & FLAG_STATIC_ANALYSIS_MSVC ) != 0 ); }
        bool IsOrbisWavePSSLC() const { return ( ( m_Flags & FLAG_ORBIS_WAVE_PSSLC ) != 0 ); }
        bool IsDiagnosticsColorAuto() const { return ( ( m_Flags & FLAG_DIAGNOSTICS_COLOR_AUTO ) != 0 ); }
        bool IsWarningsAsErrorsClangGCC() const { return ( ( m_Flags & FLAG_WARNINGS_AS_ERRORS_CLANGGCC ) != 0 ); }
        bool IsClangCl() const { return ( ( m_Flags & FLAG_CLANG_CL ) != 0 ); }
        bool IsUsingGcovCoverage() const { return ( ( m_Flags & FLAG_GCOV_COVERAGE ) != 0 ); }
        bool IsUsingDynamicDeopt() const { return ( ( m_Flags & FLAG_DYNAMIC_DEOPT ) != 0 ); }
        [[nodiscard]] bool IsNoStdInc() const { return ( ( m_Flags & FLAG_NOSTDINC ) != 0 ); }
        [[nodiscard]] bool IsNoStdIncPP() const { return ( ( m_Flags & FLAG_NOSTDINCPP ) != 0 ); }

        enum Flag : uint32_t
        {
            FLAG_CAN_BE_CACHED = 0x01,
            FLAG_CAN_BE_DISTRIBUTED = 0x02,
            FLAG_USING_PCH = 0x04,
            FLAG_GCC = 0x10,
            FLAG_MSVC = 0x20,
            FLAG_CREATING_PCH = 0x40,
            FLAG_SNC = 0x80,
            FLAG_USING_CLR = 0x100,
            FLAG_CLANG = 0x200,
            FLAG_UNITY = 0x400,
            FLAG_ISOLATED_FROM_UNITY = 0x800,
            FLAG_USING_PDB = 0x1000,
            CODEWARRIOR_WII = 0x2000,
            GREENHILLS_WIIU = 0x4000,
            FLAG_CUDA_NVCC = 0x10000,
            FLAG_INCLUDES_IN_STDERR = 0x20000,
            FLAG_QT_RCC = 0x40000,
            FLAG_WARNINGS_AS_ERRORS_MSVC = 0x80000,
            FLAG_VBCC = 0x100000,
            FLAG_STATIC_ANALYSIS_MSVC = 0x200000,
            FLAG_ORBIS_WAVE_PSSLC = 0x400000,
            FLAG_DIAGNOSTICS_COLOR_AUTO = 0x800000,
            FLAG_WARNINGS_AS_ERRORS_CLANGGCC = 0x1000000,
            FLAG_CLANG_CL = 0x2000000,
            FLAG_GCOV_COVERAGE = 0x4000000,
            FLAG_DYNAMIC_DEOPT = 0x8000000,
            FLAG_NOSTDINC = 0x10000000,
            FLAG_NOSTDINCPP = 0x20000000,
        };

        void Set( Flag flag ) { m_Flags |= flag; }
        void Clear( Flag flag ) { m_Flags &= ( ~flag ); }

        uint32_t m_Flags = 0;
    };
    const CompilerFlags & GetCompilerFlags() const { return m_CompilerFlags; }

    static CompilerFlags DetermineFlags( const CompilerNode * compilerNode,
                                         const AString & args,
                                         bool creatingPCH,
                                         bool usingPCH );
    static bool IsCompilerArg_MSVC( const AString & token, const char * arg );
    static bool IsStartOfCompilerArg_MSVC( const AString & token, const char * arg );

    bool IsCacheable() const { return m_CompilerFlags.IsCacheable(); }
    bool IsDistributable() const { return m_CompilerFlags.IsDistributable(); }
    bool IsUsingPCH() const { return m_CompilerFlags.IsUsingPCH(); }
    bool IsCreatingPCH() const { return m_CompilerFlags.IsCreatingPCH(); }
    bool IsClang() const { return m_CompilerFlags.IsClang(); }
    bool IsUnity() const { return m_CompilerFlags.IsUnity(); }
    bool IsIsolatedFromUnity() const { return m_CompilerFlags.IsIsolatedFromUnity(); }
    bool IsGCC() const { return m_CompilerFlags.IsGCC(); }
    bool IsMSVC() const { return m_CompilerFlags.IsMSVC(); }
    bool IsSNC() const { return m_CompilerFlags.IsSNC(); }
    bool IsUsingCLR() const { return m_CompilerFlags.IsUsingCLR(); }
    bool IsClangCl() const { return m_CompilerFlags.IsClangCl(); }
    bool IsUsingPDB() const { return m_CompilerFlags.IsUsingPDB(); }
    bool IsCodeWarriorWii() const { return m_CompilerFlags.IsCodeWarriorWii(); }
    bool IsGreenHillsWiiU() const { return m_CompilerFlags.IsGreenHillsWiiU(); }
    bool IsCUDANVCC() const { return m_CompilerFlags.IsCUDANVCC(); }
    bool IsIncludesInStdErr() const { return m_CompilerFlags.IsIncludesInStdErr(); }
    bool IsQtRCC() const { return m_CompilerFlags.IsQtRCC(); }
    bool IsWarningsAsErrorsMSVC() const { return m_CompilerFlags.IsWarningsAsErrorsMSVC(); }
    bool IsVBCC() const { return m_CompilerFlags.IsVBCC(); }
    bool IsUsingStaticAnalysisMSVC() const { return m_CompilerFlags.IsUsingStaticAnalysisMSVC(); }
    bool IsOrbisWavePSSLC() const { return m_CompilerFlags.IsOrbisWavePSSLC(); }
    bool IsWarningsAsErrorsClangGCC() const { return m_CompilerFlags.IsWarningsAsErrorsClangGCC(); }
    bool IsUsingGcovCoverage() const { return m_CompilerFlags.IsUsingGcovCoverage(); }
    bool IsUsingDynamicDeopt() const { return m_CompilerFlags.IsUsingDynamicDeopt(); }

    virtual void SaveRemote( IOStream & stream ) const override;
    static Node * LoadRemote( IOStream & stream );

    CompilerNode * GetCompiler() const;
    Node * GetSourceFile() const { return m_StaticDependencies[ 1 ].GetNode(); }
    CompilerNode * GetDedicatedPreprocessor() const;
#if defined( __WINDOWS__ )
    Node * GetPrecompiledHeaderCPPFile() const
    {
        ASSERT( m_CompilerFlags.IsCreatingPCH() );
        return m_StaticDependencies[ 1 ].GetNode();
    }
#endif
    ObjectNode * GetPrecompiledHeader() const;

    void GetPDBName( AString & pdbName ) const;
    void GetNativeAnalysisXMLPath( AString & outXMLFileName ) const;
    void GetGCNOPath( AString & gcnoFileName ) const;
    void GetAltObjPath( AString & altObjName ) const;

    const AString & GetPCHObjectName() const;
    const AString & GetPrecompiledHeaderName() const;
    const ObjectListNode & GetOwnerObjectList() const { return *m_OwnerObjectList; }

    void ExpandCompilerForceUsing( Args & fullArgs, const AString & pre, const AString & post ) const;

#if defined( ENABLE_FAKE_SYSTEM_FAILURE )
    // Fake system failure for tests
    enum FakeSystemFailureState : uint32_t
    {
        DISABLED,   // Feature not in use
        ENABLED,    // Feature enabled
        RACING,     // Racing
    };
    static void SetFakeSystemFailureForNextJob() { sFakeSystemFailureState.Store( static_cast<uint32_t>( FakeSystemFailureState::ENABLED ) ); }
    static bool GetFakeSystemFailureForNextJob() { return ( sFakeSystemFailureState.Load() > DISABLED ); }
#endif

protected:
    virtual BuildResult DoBuild( Job * job ) override;
    virtual BuildResult DoBuild2( Job * job, bool racingRemoteJob ) override;
    virtual bool Finalize( NodeGraph & nodeGraph ) override;

    virtual void Migrate( const Node & oldNode ) override;

    virtual uint8_t GetConcurrencyGroupIndex() const override;

    BuildResult DoBuildMSCL_NoCache( Job * job, bool useDeoptimization );
    BuildResult DoBuildWithPreProcessor( Job * job, bool useDeoptimization, bool useCache, bool useSimpleDist );
    BuildResult DoBuildWithPreProcessor2( Job * job,
                                          bool useDeoptimization,
                                          bool stealingRemoteJob,
                                          bool racingRemoteJob,
                                          bool isFollowingLightCacheMiss );
    BuildResult DoBuild_QtRCC( Job * job );
    BuildResult DoBuildOther( Job * job, bool useDeoptimization );

    bool ProcessIncludesMSCL( const char * output, uint32_t outputSize );
    bool ProcessIncludesWithPreProcessor( Job * job );

    const AString & GetCacheName( Job * job ) const;
    uint32_t GetCommandLineKey( Job * job ) const;
    bool RetrieveFromCache( Job * job );
    void WriteToCache_FromDisk( Job * job );
    void WriteToCache_FromUncompressedData( Job * job,
                                            const void * uncompressedData,
                                            uint64_t uncompressedDataSize );
    void WriteToCache_FromCompressedData( Job * job,
                                          const void * compressedData,
                                          uint64_t compressedDataSize,
                                          uint32_t compressionTimeMS );
    void GetExtraCacheFilePaths( const Job * job, Array<AString> & outFileNames ) const;

    void EmitCompilationMessage( const Args & fullArgs, bool useDeoptimization, bool stealingRemoteJob = false, bool racingRemoteJob = false, bool useDedicatedPreprocessor = false, bool isRemote = false ) const;

    enum Pass
    {
        PASS_PREPROCESSOR_ONLY,
        PASS_COMPILE_PREPROCESSED,
        PASS_COMPILE,
        PASS_PREP_FOR_SIMPLE_DISTRIBUTION,
    };
    bool BuildArgs( const Job * job, Args & fullArgs, Pass pass, bool useDeoptimization, bool useShowIncludes, bool useSourceMapping, bool finalize, const AString & overrideSrcFile = AString::GetEmpty() ) const;

    BuildResult BuildPreprocessedOutput( const Args & fullArgs, Job * job, bool useDeoptimization ) const;
    bool LoadStaticSourceFileForDistribution( const Args & fullArgs, Job * job, bool useDeoptimization ) const;
    void TransferPreprocessedData( const char * data, size_t dataSize, Job * job ) const;
    bool WriteTmpFile( Job * job, AString & tmpDirectory, AString & tmpFileName ) const;
    BuildResult BuildFinalOutput( Job * job, const Args & fullArgs ) const;

    static void HandleSystemFailures( Job * job, int result, const AString & stdOut, const AString & stdErr );
    bool ShouldUseDeoptimization() const;
    bool GetDeoptimizeWritableFiles() const;
    bool GetDeoptimizeWritableFilesWithToken() const;
    friend class ClientToWorkerConnection;
    bool ShouldUseCache() const;
    [[nodiscard]] bool IsDistributionAllowed() const;
    ArgsResponseFileMode GetResponseFileMode() const;
    bool GetVBCCPreprocessedOutput( ConstMemoryStream & outStream ) const;

    void DoClangUnityFixup( Job * job ) const;

    void CreateDriver( ObjectNode::CompilerFlags flags,
                       const AString & remoteSourceRoot,
                       UniquePtr<CompilerDriverBase> & outDriver ) const;

    virtual const AString & GetCommandLine( bool useDedicatedPreprocessor,
                                            bool useDeoptimization ) const;

    friend class FunctionObjectList;

    class CompileHelper
    {
    public:
        explicit CompileHelper( bool handleOutput = true, const Atomic<bool> * abort = nullptr );
        ~CompileHelper();

        // start compilation
        Node::BuildResult SpawnCompiler( Job * job,
                                         const AString & name,
                                         const CompilerNode * compilerNode,
                                         const AString & compiler,
                                         const Args & fullArgs,
                                         const char * workingDir = nullptr );

        // determine overall result
        int GetResult() const { return m_Result; }

        // access output/error
        const AString & GetOut() const { return m_Out; }
        const AString & GetErr() const { return m_Err; }
        bool HasAborted() const { return m_Process.HasAborted(); }

        // Copies not allowed
        explicit CompileHelper( CompileHelper & other ) = delete;
        CompileHelper & operator=( CompileHelper & other ) = delete;

    private:
        bool m_HandleOutput;
        Process m_Process;
        AString m_Out;
        AString m_Err;
        int m_Result;
    };

    // Exposed Properties
    friend class ObjectListNode;
    AString m_CompilerInputFile;

    // Internal State
    CompilerFlags m_CompilerFlags;
    CompilerFlags m_PreprocessorFlags;
    uint32_t m_OwnerObjectListHash = 0; // Hash of relevant options from OwnerObjectList
    uint64_t m_PCHCacheKey = 0;
    uint64_t m_LightCacheKey = 0;
    ObjectListNode * m_OwnerObjectList = nullptr;

    // Not serialized
    Array<AString> m_Includes;

#if defined( ENABLE_FAKE_SYSTEM_FAILURE )
    // Fake system failure for tests
    static Atomic<uint32_t> sFakeSystemFailureState;
#endif
};

//------------------------------------------------------------------------------
class ObjectNodeRemote : public ObjectNode
{
public:
    ObjectNodeRemote( AString && objectName,
                      NodeProxy * srcFile,
                      AString && compilerOptions,
                      uint32_t flags );
    virtual ~ObjectNodeRemote() override;

protected:
    virtual const AString & GetCommandLine( bool useDedicatedPreprocessor,
                                            bool useDeoptimization ) const override;

    AString m_CompilerOptions;
};

//------------------------------------------------------------------------------
