// Copyright (c) Microsoft Corporation. 
// Licensed under the MIT license.

#pragma once
#include "CoreMinimal.h"
#include "Misc/Attribute.h"
#include "Widgets/DeclarativeSyntaxSupport.h"
#include "Input/Reply.h"
#include "Widgets/SWidget.h"
#include "Misc/NotifyHook.h"
#include "Misc/TextFilter.h"
#include "Widgets/SCompoundWidget.h"
#include "EditorUndoClient.h"
#include "Widgets/Views/STableRow.h"
#include "Widgets/Views/STreeView.h"
#include "Widgets/Views/SListView.h"
#include "Widgets/Views/STableViewBase.h"
#include "TreeItemID.h"
#include "Components/DrawSphereComponent.h"
#include "SimplygonAssetUserData.h"
#include "StandinUtilitiesModule.h"


class AStandInMeshSelectionActor;
class UWorld;
class UDrawSphereComponent;
class IDetailsView;
class AStandInMeshActor;
class AActor;


//class IHierarchicalLODUtilities;

namespace StandInsOutliner
{
	struct ITreeItem;
	struct FDragValidationInfo;
	struct FDragDropPayload;
	typedef TSharedPtr<ITreeItem> FTreeItemPtr;
	typedef TSharedRef<ITreeItem> FTreeItemRef;
	typedef TTextFilter< const ITreeItem& > TreeItemTextFilter;
	/**
	* Outliner action class used for making changing to the Outliner's treeview
	*/
	struct FOutlinerAction
	{
		enum ActionType
		{
			AddItem,
			RemoveItem,
			MoveItem
		};

		FOutlinerAction(ActionType InType, FTreeItemPtr InItem) : Type(InType), Item(InItem) {};
		FOutlinerAction(ActionType InType, FTreeItemPtr InItem, FTreeItemPtr InParentItem) : Type(InType), Item(InItem), ParentItem(InParentItem) {};

		ActionType Type;
		FTreeItemPtr Item;
		FTreeItemPtr ParentItem;
	};

	/**
	* Implements the profiler window.
	*/
	class SStandInsOutliner : public SCompoundWidget, public FNotifyHook, public FEditorUndoClient
	{
		typedef STreeView<FTreeItemPtr> SReplacementLODTree;
		friend struct FStandInMeshActorItem;
		friend struct FReplacementLODTreeWidgetItem;
		friend struct FStaticMeshActorItem;
		friend class SStandInsOutlinerWidgetItem;

	public:
		/** Default constructor. */
		SStandInsOutliner();

		/** Virtual destructor. */
		virtual ~SStandInsOutliner();

		SLATE_BEGIN_ARGS(SStandInsOutliner) {}
		SLATE_END_ARGS()

		/**
		* Constructs this widget.
		*/
		void Construct(const FArguments& InArgs);

		/** Creates the panel's ButtonWidget rows */
		TSharedRef<SWidget> CreateButtonWidgets();

		/** Creates the panel's Tree view widget*/
		TSharedRef<SWidget> CreateTreeviewWidget();

		/** Initializes and creates the settings view */
		void CreateSettingsView();

		//~ Begin SCompoundWidget Interface
		virtual void Tick(const FGeometry& AllottedGeometry, const double InCurrentTime, const float InDeltaTime) override;
		virtual void OnMouseEnter(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent) override;
		virtual void OnMouseLeave(const FPointerEvent& MouseEvent) override;
		virtual FReply OnKeyDown(const FGeometry& MyGeometry, const FKeyEvent& InKeyEvent) override;
		virtual FReply OnDrop(const FGeometry& MyGeometry, const FDragDropEvent& DragDropEvent) override;
		virtual FReply OnDragOver(const FGeometry& MyGeometry, const FDragDropEvent& DragDropEvent)  override;
		//virtual FReply OnDragDetected(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent) override;
		virtual void OnDragEnter(const FGeometry& MyGeometry, const FDragDropEvent& DragDropEvent) override;
		//~ End SCompoundWidget Interface

		//~ Begin FEditorUndoClient Interface
		virtual void PostUndo(bool bSuccess) override;
		virtual void PostRedo(bool bSuccess) override { PostUndo(bSuccess); }
		// End of FEditorUndoClient	

		/** Button handlers */
		FReply BuildAllClicked();
		FReply RebuildAllClicked();
		FReply CreateStandinClicked();

		FReply RetrieveActors();
		FReply HandleForceRefresh();
		/** End button handlers */

	private:
		/** Registers all the callback delegates required for keeping the treeview sync */
		void RegisterDelegates();

		/** De-registers all the callback delegates required for keeping the treeview sync */
		void DeregisterDelegates();

		/** Called when the map has changed*/
		void OnMapChange(uint32 MapFlags);

		/** Called when the current level has changed */
		void OnNewCurrentLevel();

		/** Called when a new map is being loaded */
		void OnMapLoaded(const FString&  Filename, bool bAsTemplate);

		void OnDeleteActors();

		/** Called by the engine when a level is added to the world. */
		void OnLevelAdded(ULevel* InLevel, UWorld* InWorld);

		/** Called by the engine when a level is removed from the world. */
		void OnLevelRemoved(ULevel* InLevel, UWorld* InWorld);

	protected:
		/** Build all replacement actors */
		void BuildStandInMeshActor(bool bRebuild = false);

		/** Selects Replacement Actor*/
		void SelectStandinLODActor();

		/** Selects all actors that are being replaced*/
		void SelectContainedActors();

		/** Removes all static mesh actors contained in the replacement*/
		void RemoveStandInMeshActor();

		/** Save the static mesh actor and */
		void SaveToPackage();

		/** Removes all static mesh actors contained in the replacement*/
		void RemoveFromStandinActor();

		/** Tag Items as Clipping Geometry*/
		void AddSimplygonUseAssetData(ESimplygonMetaTagType InTag);

		/**  Remove Simplygon User AssetData */
		void RemoveSimplygonUserAssetData();

		void PopulateSearchStrings(const ITreeItem& Item, TArray< FString >& OutSearchStrings) const;

	protected:
		const bool UpdateCurrentWorld();

	protected:

		FReply HandleDrop(const FDragDropEvent& DragDropEvent, FDragValidationInfo& ValidationInfo, bool bApplyDrop);
		FDragValidationInfo ValidateDrop(FDragDropPayload& DraggedObjects) const;

		/** Tree view callbacks */

		/**
		* Generates a Tree view row for the given Tree view Node
		*
		* @param InReflectorNode - Node to generate a row for
		* @param OwnerTable - Owning table of the InReflectorNode
		* @return TSharedRef<ITableRow>
		*/
		TSharedRef<ITableRow> OnOutlinerGenerateRow(FTreeItemPtr InReflectorNode, const TSharedRef<STableViewBase>& OwnerTable);

		/**
		* Treeview callback for retrieving the children of a specific TreeItem
		*
		* @param InParent - Parent item to return the children from
		* @param OutChildren - InOut array for the children
		*/
		void OnOutlinerGetChildren(FTreeItemPtr InParent, TArray<FTreeItemPtr>& OutChildren);

		/**
		* Handles the event fired when selection with the HLOD Tree view changes
		*
		* @param TreeItem - Selected node(s)
		* @param SelectionInfo - Type of selection change
		*/
		void OnOutlinerSelectionChanged(FTreeItemPtr TreeItem, ESelectInfo::Type /*SelectionInfo*/);

		/**
		* Handles double click events from the HLOD Tree view
		*
		* @param TreeItem - Node which was double-clicked
		*/
		void OnOutlinerDoubleClick(FTreeItemPtr TreeItem);

		/** Open a context menu for this scene outliner */
		TSharedPtr<SWidget> OnOpenContextMenu();

		/**
		* Handles item expansion events from the HLOD tree view
		*
		* @param TreeItem - Item which expansion state was changed
		* @param bIsExpanded - New expansion state
		*/
		void OnItemExpansionChanged(FTreeItemPtr TreeItem, bool bIsExpanded);

		void HandleStandInMeshGenerated();

		bool HasStandinActors() const;
		bool HasStaticMeshActorsInLevel() const;


		void OnTreeSearchBoxTextChanged(const FText& InFilterText);
		void OnTreeSearchTextCommitted(const FText& InFilterText, ETextCommit::Type CommitInfo);

		/** End of Tree view callbacks */

	private:
		/** Starts the Editor selection batch */
		void OnLevelSelectionChanged(UObject* Obj);

		void StartSelection();

		/** Empties the current Editor selection */
		void EmptySelection();

		/** Destroys the created selection actors */
		void DestroySelectionActors();

		/** Select actor in viewport */
		void SelectActorInViewport(AActor* Actor, const uint32 SelectionDepth = 0);

		/** Select replacement actor and all sub actors in viewport */
		void SelectSelfAndSubActorsInViewport(AStandInMeshActor* ReplacementActor, const uint32 SelectionDepth = 0, bool bSelectSelf = false);

		/** Ends the Editor selection batch, bChange determines whether or not there was an actual change and call NoteSelectionChange */
		void EndSelection(const bool bChange);


		/**
		* Creates a ASelectionActor for the given actor, "procedurally" drawing its bounds
		*
		* @param Actor - Actor to create the SelectionActor for
		* @return UDrawSphereComponent*
		*/
		UDrawSphereComponent* CreateBoundingSphereForActor(AActor* Actor);

		/** End of Broadcast event delegates */
	private:
		/** Tells the scene outliner that it should do a full refresh, which will clear the entire tree and rebuild it from scratch. */
		void FullRefresh();

		/** Populates the HLODTreeRoot array and consequently the Treeview */
		void Populate();

		/** Clears and resets all arrays and maps containing cached/temporary data */
		void ResetCachedData();

		/** Structure containing information relating to the expansion state of parent items in the tree */
		typedef TMap<FTreeItemID, bool> FParentsExpansionState;

		/** Gets the current expansion state of parent items */
		TMap<FTreeItemID, bool> GetParentsExpansionState() const;

		/** Updates the expansion state of parent items after a repopulate, according to the previous state */
		void SetParentsExpansionState(const FParentsExpansionState& ExpansionStateInfo) const;

		/**
		* Adds a new Treeview item
		*
		* @param InItem - Item to add
		* @param InParentItem - Optional parent item to add it to
		* @return const bool
		*/
		const bool AddItemToTree(FTreeItemPtr InItem, FTreeItemPtr InParentItem);

		/**
		* Moves a TreeView item around
		*
		* @param InItem - Item to move
		* @param InParentItem - New parent for InItem to move to
		*/
		void MoveItemInTree(FTreeItemPtr InItem, FTreeItemPtr InParentItem);

		/**
		* Removes a TreeView item
		*
		* @param InItem - Item to remove
		*/
		void RemoveItemFromTree(FTreeItemPtr InItem);

		/**
		* Selects a Treeview item
		*
		* @param InItem - Item to select
		*/
		void SelectItemInTree(FTreeItemPtr InItem);

		//FReply OnUseEditorStandinClicked();
		FReply OnUseGameStandinClicked();

		void OnObjectPostEditChange(UObject* Object, FPropertyChangedEvent& PropertyChangedEvent);

		float GetSourceSize() const;
		float GetStandinSize() const;
		
	private:
		/** Whether or not we need to do a refresh of the Tree view*/
		bool bNeedsRefresh;
		
		/** World instance we are currently representing/mirroring in the panel */
		UWorld* CurrentWorld;

		/** Tree view nodes */
		TArray<FTreeItemPtr> ReplacementLODTreeRoot;
		
		/** Currently selected Tree view nodes*/
		TArray<FTreeItemPtr> SelectedNodes;

		/** Currently selected Tree view nodes*/
		TArray<TWeakObjectPtr<UObject>> SelectedActorObjects;
		
		/** ReplacmeentLODActor Treeview widget*/
		TSharedPtr<SReplacementLODTree> TreeView;
		/** Property viewing widget */
		TSharedPtr<IDetailsView> SettingsView;
		/** Content panel widget */
		TSharedPtr<SVerticalBox> MainContentPanel;

		/** Map containing all the nodes with their corresponding keys */
		TMultiMap<FTreeItemID, FTreeItemPtr> TreeItemsMap;

		/** Array of pending OutlinerActions */
		TArray<FOutlinerAction> PendingActions;

		/** Array containing all the nodes */
		TArray<FTreeItemPtr> AllNodes;

		/** Array of SelectionActors created for current HLOD selection*/
		TArray<AStandInMeshSelectionActor*> SelectionActors;
		
		/** Array with flags for each LOD level (whether or not all their Clusters/LODActors have their meshes built) */
		TArray<bool> ReplacementLODActorBuildFlags;
		/** Array of ReplacementLODActors in World*/
		TArray<AStandInMeshActor*> ReplacementLODActors;

		/** Cached pointer to Standin utilities */
		IStandinUtilitiesModule* StandinUtilitiesModule;

		/* Widget containing the filtering text box */
		TSharedPtr< SSearchBox > FilterTextBoxWidget;

		/** A collection of filters used to filter the displayed actors and folders in the scene outliner */
		//TSharedPtr< FOutlinerFilters > Filters;

		/** The TextFilter attached to the SearchBox widget of the Scene Outliner */
		TSharedPtr< TreeItemTextFilter > SearchBoxFilter;

		/** Reference to Standin Actor*/
		AStandInMeshActor* CurrentStandin;

	};
};
