// Copyright (c) 2022 Codemongous LLC. All Rights Reserved.
#pragma once

#include "DependencyNode.h"
#include <functional>
#include "IConversionContextManager.h"
#include "IFileBackupManager.h"
#include "ISourcifyCoreSettings.h"
#include "ISCW_UBlueprint.h"
#include "ISCW_UClass.h"
#include "ISCW_UnrealEngine.h"
#include "ISCW_UObject.h"
#include "ISCW_UUserDefinedEnum.h"
#include "ISCW_UUserDefinedStruct.h"
#include <memory>
#include <optional>
#include <set>
#include "SourcifyCoreDefines.h"
#include <string>
#include <vector>

using std::function;
using std::make_shared;
using std::optional;
using std::set;
using std::shared_ptr;
using std::vector;
using std::wstring;


namespace Sourcify
{
	struct BlueprintDependencies
	{
		vector<ISCW_UBlueprintPtr> BPs;
		vector<ISCW_UUserDefinedStructPtr> Structs;
		vector<ISCW_UUserDefinedEnumPtr> Enums;
		vector<ISCW_UClassPtr> Interfaces;
        set<ISCW_UPackagePtr> PackagesToCheckout;

		shared_ptr<DependencyNode> DependencyGraph = make_shared<DependencyNode>();

        vector<ISCW_UPackagePtr> GetPackagesToCheckout() const
        {
            set<ISCW_UPackagePtr> PackagesSet;
            auto CollectPackages = [&PackagesSet](auto&& CollectPackages, const DependencyNode* Node) -> void
                {
                    if (!Node->Object)
                    {
                        return;
                    }

                    if (ISCW_UPackagePtr PackagePtr = Node->Object->GetPackage())
                    {
                        PackagesSet.insert(PackagePtr);
                    }

                    for (const DependencyNode::ReferenceInfo& Referencee : Node->Referencees)
                    {
                        CollectPackages(CollectPackages, Referencee.Node.get());
                    }
                };

            CollectPackages(CollectPackages, DependencyGraph.get());
            return vector<ISCW_UPackagePtr>(PackagesSet.begin(), PackagesSet.end());
        }

        vector<ISCW_UPackagePtr> GetUnsavedAssets() const
        {
            vector<ISCW_UPackagePtr> UnsavedAssets;
            auto GetUnsavedAsset = [&UnsavedAssets](auto&& GetUnsavedAsset, const DependencyNode* Node) -> void
                {
                    if (!Node->Object)
                    {
                        return;
                    }

                    if (ISCW_UPackagePtr PackagePtr = Node->Object->GetPackage())
                    {
                        if (PackagePtr->IsDirty())
                        {
                            UnsavedAssets.push_back(PackagePtr);
                        }
                    }

                    for (const DependencyNode::ReferenceInfo& Referencee : Node->Referencees)
                    {
                        GetUnsavedAsset(GetUnsavedAsset, Referencee.Node.get());
                    }
                };

            GetUnsavedAsset(GetUnsavedAsset, DependencyGraph.get());
            return UnsavedAssets;
        }

        bool IsValid() const
        {
            return DependencyGraph && DependencyGraph->Object;
        }
	};

	struct FormatRule
	{
		enum class ERuleType { Class, Function, Property, Struct, Enum, Delegate };
		ERuleType Type;
		wstring Rule;
	};

    struct IniReference
    {
        wstring Ini;
        wstring Section;
        wstring Key;
    };

    struct PackageReference
    {
        ISCW_UPackageConstPtr PackagePtr;
        optional<wstring> SourcifiedClassName;
    };

	inline bool operator<(PackageReference const& Lhs, PackageReference const& Rhs)
	{
		return Lhs.PackagePtr->Less(*Rhs.PackagePtr);
	}

    struct FunctionNameConflictRename
    {
        wstring OriginalName;
        wstring NewName;
        wstring ClassName;

        wstring ConflictingParentClassName;
        // Absolute path to the parent class header
        wstring ConflictingParentHeaderPath;
        // 1-based line number in header, or -1 if unknown
        int32_t ConflictingParentLineNumber = -1;
    };

    struct Results
    {
        enum class EResultType { Unknown, Success, Fail, Warn, NoAction };
        EResultType Type;
        wstring Message;

		// These are the ini references that are still using the non-sourcified asset and require manual fix-up
		// The key is the non-sourcified asset path
		map<wstring, vector<IniReference>> NonSourcifiedAssetIniReferences;

        // BPs that were Sourcified, thus reparented to C++ classes
        vector<PackageReference> SourcifiedBPs;

        // Assets that contain references that were updated to newly Sourcified types
        set<PackageReference> PackagesContainingSourcifiedReferences;
        
        // Assets that were fully sourcified and should be deleted (enums and structs)
        vector<PackageReference> PackagesThatShouldBeDeleted;

        // Assets whose type is not supported
		set<wstring> UnsupportedTypeNames;

        // Functions that were auto-renamed to avoid hiding parent class functions with the same name
        vector<FunctionNameConflictRename> FunctionNameConflictRenames;

        bool Succeeded() const
        {
            return Type == EResultType::Success || Type == EResultType::NoAction || Type == EResultType::Warn;
        }
        bool Failed() const
        {
            return Type == EResultType::Fail;
        }
        operator bool() const
        {
            return Succeeded();
        }

		void ClearReferences()
		{
			NonSourcifiedAssetIniReferences.clear();
			SourcifiedBPs.clear();
			PackagesContainingSourcifiedReferences.clear();
			PackagesThatShouldBeDeleted.clear();
			FunctionNameConflictRenames.clear();
		}
    };

    enum class LicenseStatus : uint8_t
    {
	    NoError = 0,
        Error
    };

    enum class IgnoreConversionSettingsCache : uint8_t
    {
	    No = 0,
        Yes
    };

    struct LicenseHolderInfo
    {
		wstring LicenseKey;
		wstring LicenseExpirationDate;
		wstring Email;
		wstring FirstName;
		wstring LastName;
		wstring Company;
    };

   class ISourcifyCore
    {
    public:
		ISourcifyCore() = default;
		virtual ~ISourcifyCore() = default;
        virtual const BlueprintDependencies& PreSourcify(ISCW_UObjectPtr Object, IgnoreConversionSettingsCache IgnoreCache = IgnoreConversionSettingsCache::No) = 0;
		virtual bool IsPreSourcifying() const = 0;
		virtual Results Sourcify(function<void(Results)> CompletionCallback) = 0;
        virtual ISourcifyCoreSettings* GetCoreSettings() const = 0;
        virtual IConversionContextManager* GetConversionContextManager() const = 0;
        virtual LicenseStatus GetLicenseStatus() const = 0;
    	virtual const wstring& GetLicenseStatusMessage() const = 0;
        virtual LicenseStatus ActivateLicense(const wstring& InKey) const = 0;
        virtual LicenseStatus DeactivateCurrentLicense() const = 0;
        virtual LicenseStatus CheckLicense() const = 0;
        virtual const LicenseHolderInfo& GetLicenseHolderInfo() const = 0;
		virtual bool IsLicenseActionAllowed() const = 0;
		virtual bool IsTrialSourcificationAllowed(const DependencyNode& Node) const = 0;
        virtual void ForEachDependencyNode(function<void(DependencyNode&)> Callback) = 0;
        virtual IFileBackupManager* GetFileBackupManager() const = 0;
    };

    struct SC
    {
		static SOURCIFY_CORE_DLL ISourcifyCore* CreateCoreInstance(ISCW_UnrealEngine& InIUE);
    };
 }

#if SOURCIFY_CORE_LINKED_WITH_UE
class FSourcifyCoreModule : public IModuleInterface
{
public:

    /** IModuleInterface implementation */
    virtual void StartupModule() override;
    virtual void ShutdownModule() override;

private:
    
    // Used to set to known values before tests run and restore current settings after tests run
    Sourcify::ISourcifyCoreSettings* CoreSettings = nullptr;

    // Backups of current settings before changing to safe values for tests
    bool BackupAutoSaveBlueprints;
	bool BackupDeclareDelegatesInsideClass;
	Sourcify::SourcifyCoreSettingsEmittedCodeIndentType BackupIndentType;
	int32 BackupNumIndentSpaces;
	int32 BackupNumLinesBetweenTypeDeclarations;
	int32 BackupNumLinesBetweenFunctionAndPropertyDeclarations;
	int32 BackupNumLinesBetweenPropertyDeclarations;
	int32 BackupNumLinesBetweenFunctionDeclarations;
	int32 BackupNumLinesBetweenClassAccessBlocks;
	int32 BackupNumZerosAfterDecimalPlace;
	int32 BackupNumSpacesAroundEqualSignInMetaTags;
    vector<wstring> BackupAssetPrefixesToIgnore;
    bool BackupEnsurePropertiesHaveDefaultValues;
	Sourcify::SourcifyCoreSettingsToolTipCommentStyle BackupToolTipCommentStyle;

private:

    // Called before each test (including tests outside of Sourcify)
    void OnTestStart(FAutomationTestBase* Test);
	void OnTestEnd(FAutomationTestBase* Test);

    // Backs up current settings values and sets settings values to known good values for tests
    void SetTestSettings();
    // Restores settings values to values that were set before a test was run
    void RestorePreTestSettings();
};
#endif