using System;
using System.Collections;
using System.Text;
using System.Threading.Tasks;
using Beefy;
using Beefy.widgets;
using Beefy.theme.dark;
using Beefy.gfx;
using Beefy.events;
using IDE.Compiler;
using IDE.Util;
using Beefy.utils;
using System.Diagnostics;
using System.IO;
using System.Threading;

namespace IDE.ui
{
    public class SymbolReferenceHelper : Widget
    {
        public enum Kind
        {
            Rename,
            ShowFileReferences,
			FindAllReferences,
			GoToDefinition
        }

        struct ReplaceSpan
        {
            public int32 mSpanStart;
            public int32 mSpanLength;
        }

        class ReplaceSymbolData
        {
            public FileEditData mFileEditData;
            public List<ReplaceSpan> mSpans = new List<ReplaceSpan>() ~ delete _;
			public bool mIsLocked;
        }

		enum BackgroundKind
		{
			None,
			GettingSymbolReferences
		}

        public bool mInitialized;
        public bool mFailed;
		public bool mGettingSymbolInfo;
        public bool mStartedWork;
		public bool mStartingWork;
		public bool mWantsClose;
        public SourceViewPanel mSourceViewPanel;
		public bool mAwaitingGetSymbolInfo;
		bool mOwnsParser;
		BfParser mParser ~ if (mOwnsParser) delete _;
		BackgroundKind mBackgroundKind;
        ResolveParams mResolveParams = new ResolveParams() ~ delete _;
        int32 mCursorPos;
        double mVertPos;
        BfPassInstance mPassInstance;
        BfResolvePassData mResolvePassData;
		bool mUsingResolvePassData;
        String mNewReplaceStr = new String() ~ delete _;
        String mReplaceStr = new String() ~ delete _;
        String mOrigReplaceStr = new String() ~ delete _;
		String mModifiedParsers ~ delete _;
		bool mRenameHadChange;
        bool mIgnoreTextChanges;
        bool mTextChanged;
        int32 mStartIdx;
        int32 mEndIdx;
        int32 mLastDeletedIdx = -1;
        bool mSkipNextUpdate;
		bool mDoLock;
        bool mDidRevert;
		bool mClosed;
		bool mOverrideSpanLengths;

        int32 mUpdateTextCount = 0;
        public Kind mKind;

        List<ReplaceSymbolData> mUpdatingProjectSources ~ DeleteContainerAndItems!(_);
        
		public bool HasStarted
		{
			get
			{
				return mStartedWork;
			}
		}

		public bool IsStarting
		{
			get
			{
				return mStartingWork;
			}
		}
		
		public bool IsRenaming
		{
			get
			{
				return mKind == Kind.Rename;
			}
		}

		public bool IsLocked
		{
			get
			{
				return mDoLock;
			}
		}

		/*public this()
		{

			Debug.WriteLine("SymbolReferenceHelper this {0}", this);
		}

		public ~this()
		{
			Debug.WriteLine("SymbolReferenceHelper ~this {0}", this);
		}*/

        public void Init(SourceViewPanel sourceViewPanel, Kind kind)
        {
            mSourceViewPanel = sourceViewPanel;
            mKind = kind;
        
            mCursorPos = (int32)sourceViewPanel.mEditWidget.Content.CursorTextPos;
            mVertPos = sourceViewPanel.mEditWidget.mVertPos.mDest;

			mAwaitingGetSymbolInfo = true;
			CheckGetSymbolInfo(kind == .ShowFileReferences);
        }


		void WaitForResolvedAll()
		{
			while (!gApp.mBfResolveCompiler.HasResolvedAll())
			{
				gApp.mBfResolveCompiler.WaitForBackground();
				gApp.mBfResolveCompiler.Update();
			}
		}
		
		bool CheckGetSymbolInfo(bool force)
		{
			if (!mAwaitingGetSymbolInfo)
				return true;

			if (!force)
			{
				if (!gApp.mBfResolveCompiler.HasResolvedAll())
					return false;
			}

			mAwaitingGetSymbolInfo = false;
			mSourceViewPanel.Classify(.GetSymbolInfo);
			mInitialized = true;
			mGettingSymbolInfo = true;
			return true;
		}

        public void SetSymbolInfo(String symbolInfo)
        {
			mGettingSymbolInfo = false;
            String foundStr = null;
            bool foundSymbol = false;
			bool isTypeRef = false;

            for (var infoLine in symbolInfo.Split('\n'))
            {
                var lineDataItr = infoLine.Split('\t');
				var dataType = lineDataItr.GetNext().Get();
				switch (dataType)
				{
	            case "insertRange":
					var dataItr = lineDataItr.GetNext().Get().Split(' ');
                    mStartIdx = int32.Parse(dataItr.GetNext().Get());
                    mEndIdx = int32.Parse(dataItr.GetNext().Get());
					Debug.Assert(mEndIdx >= mStartIdx);
                    if (mEndIdx > mStartIdx)
					{
						foundStr = scope:: String();
                        mSourceViewPanel.mEditWidget.Content.ExtractString(mStartIdx, mEndIdx - mStartIdx, foundStr);
					}
	            case "localId":
                    int32 localId = int32.Parse(lineDataItr.GetNext().Get());
                    mResolveParams.mLocalId = localId;
                    foundSymbol = true;
	            case "typeRef":
					mOverrideSpanLengths = true;
					isTypeRef = true;
                    mResolveParams.mTypeDef = new String(lineDataItr.GetNext().Get());
                    foundSymbol = true;
	            case "fieldRef":
                    mResolveParams.mTypeDef = new String(lineDataItr.GetNext().Get());
                    mResolveParams.mFieldIdx = int32.Parse(lineDataItr.GetNext().Get());
                    foundSymbol = true;
                case "methodRef", "ctorRef":
                    mResolveParams.mTypeDef = new String(lineDataItr.GetNext().Get());
                    mResolveParams.mMethodIdx = int32.Parse(lineDataItr.GetNext().Get());
                    foundSymbol = true;
					if (dataType == "ctorRef")
					{
						// For ctor refs, the method name is really 'this' so we can't rename it...
						if (mKind != .FindAllReferences)
							foundSymbol = false;
					}
                case "invokeMethodRef":
                    if (mResolveParams.mTypeDef == null)
                    {
                        mResolveParams.mTypeDef = new String(lineDataItr.GetNext().Get());
                        mResolveParams.mMethodIdx = int32.Parse(lineDataItr.GetNext().Get());
                        foundSymbol = true;
                    }
                case "propertyRef":
                    mResolveParams.mTypeDef = new String(lineDataItr.GetNext().Get());
                    mResolveParams.mPropertyIdx = int32.Parse(lineDataItr.GetNext().Get());
                    foundSymbol = true;
				case "typeGenericParam":
				    mResolveParams.mTypeGenericParamIdx = int32.Parse(lineDataItr.GetNext().Get());
				case "methodGenericParam":
				    mResolveParams.mMethodGenericParamIdx = int32.Parse(lineDataItr.GetNext().Get());
				case "namespaceRef":
					mResolveParams.mNamespace = new String(lineDataItr.GetNext().Get());
					foundSymbol = true;
				case "defLoc":
					if (mKind == .Rename)
					{
						StringView filePath = lineDataItr.GetNext().Get();
						let editData = gApp.GetEditData(scope String(filePath), false, false);
						if (editData != null)
						{
							for (var projectSource in editData.mProjectSources)
							{
								if (projectSource.mProject.mLocked)
								{
									gApp.Fail(scope String()..AppendF("Symbol definition is located in locked project '{}' and cannot be renamed until the lock is removed.", projectSource.mProject.mProjectName));
									mFailed = true;
									Close();
									return;
								}
							}

							if (editData.IsLocked())
							{
								gApp.Fail(scope String()..AppendF("Symbol definition is located in locked file '{}' and cannot be renamed until the lock is removed.", filePath));
								mFailed = true;
								Close();
								return;
							}
						}
					}
				}
            }

            if ((!foundSymbol) || (foundStr == null))
            {
                if ((mKind == .Rename) || (mKind == .FindAllReferences))
                    IDEApp.sApp.Fail("Unable to locate symbol");
                mFailed = true;
				Close();
                return;
            }

			if (foundStr != null)
            	mReplaceStr.Set(foundStr);

			if ((isTypeRef) && (mReplaceStr.EndsWith("Attribute")))
			{
				mReplaceStr.RemoveFromEnd("Attribute".Length);
				mEndIdx = mStartIdx + (.)mReplaceStr.Length;
			}

			if ((mKind == .Rename) && (mEndIdx > 0))
			{
				var ewc = (SourceEditWidgetContent)mSourceViewPanel.mEditWidget.mEditWidgetContent;
				for (int i = mStartIdx; i < mEndIdx; i++)
				{
					ewc.mData.mText[i].mDisplayFlags |= (uint8)(SourceElementFlags.SymbolReference);
				}

				// We do the full-selection in the normal case, but if we've moved the cursor since starting the rename
				//  then that would have removed the selection anyway so we don't do the full-select in that case
				if (ewc.CursorTextPos == mCursorPos)
				{
					ewc.mSelection = EditSelection(mStartIdx, mEndIdx);
					ewc.CursorTextPos = mEndIdx;
				}
			}

            mNewReplaceStr.Set(mReplaceStr);
            mOrigReplaceStr.Set(mReplaceStr);
        }

		void PrintAllReferences()
		{
			gApp.ShowFindResults(false);

			gApp.mFindResultsPanel.Clear();
			gApp.mFindResultsPanel.QueueLine("Symbol results:");

			for (int32 sourceIdx = 0; sourceIdx < mUpdatingProjectSources.Count; sourceIdx++)            
			{
			    var replaceSymbolData = mUpdatingProjectSources[sourceIdx];
			    //var projectSource = replaceSymbolData.mProjectSource;
			    //var editData = IDEApp.sApp.GetEditData(replaceSymbolData.mProjectSource, true);
				var editData = replaceSymbolData.mFileEditData;

				List<int32> sortedIndices = scope List<int32>();

			    for (int32 i = 0; i < replaceSymbolData.mSpans.Count; i++)
			    {
			        int32 spanStart = replaceSymbolData.mSpans[i].mSpanStart;
			        //int32 spanLen = replaceSymbolData.mSpans[i].mSpanLength;
			        sortedIndices.Add(spanStart);
				}

				sortedIndices.Sort(scope (a, b) => b - a);

				int32 lineStart = 0;
				int32 lineNum = 0;
				bool wantsLine = false;
				
				var editWidgetContent = editData.mEditWidget.mEditWidgetContent;
				for (int32 idx < editWidgetContent.mData.mTextLength)
				{
					char8 c = editWidgetContent.mData.mText[idx].mChar;
					if (c == '\n')
					{
						if (wantsLine)
						{
							String lineStr = scope String();
							editWidgetContent.ExtractString(lineStart, idx - lineStart, lineStr);

							//String fileName = editData.mFilePath;
							//String fileName = scope String();
							//projectSource.GetFullImportPath(fileName);

							gApp.mFindResultsPanel.QueueLine(editData, lineNum, 0, lineStr);
							
							wantsLine = false;
						}
						lineNum++;
						lineStart = idx + 1;
					}

					if ((sortedIndices.Count > 0) && (idx == sortedIndices[sortedIndices.Count - 1]))
					{
						sortedIndices.PopBack();
						wantsLine = true;
					}
				}
				//editData.mEditWidget.Index
			}
		}

		void DoWork()
		{
			//TODO:
			//Thread.Sleep(2000);

			Debug.Assert(mStartingWork);

			var bfSystem = IDEApp.sApp.mBfResolveSystem;
			var bfCompiler = IDEApp.sApp.mBfResolveCompiler;

			bfSystem.Lock(2);
			mUsingResolvePassData = true;
			bfCompiler.GetSymbolReferences(mPassInstance, mResolvePassData, mModifiedParsers);
			mUsingResolvePassData = false;
			bfSystem.Unlock();
		}

        void StartWork()
        {
            var bfSystem = IDEApp.sApp.mBfResolveSystem;
            var bfCompiler = IDEApp.sApp.mBfResolveCompiler;

			if (mGettingSymbolInfo)
			{
				gApp.Fail("Cannot rename symbols here");
				mGettingSymbolInfo = false;
				return;
			}

			Debug.Assert(!mGettingSymbolInfo);

			StopWork();
			
			mStartedWork = true;
			mStartingWork = true;

			Debug.Assert(mParser == null);
            if ((mKind == Kind.ShowFileReferences) || (mResolveParams.mLocalId != -1))
            {                
                mParser = IDEApp.sApp.mBfResolveSystem.FindParser(mSourceViewPanel.mProjectSource);
				if ((mResolveParams != null) && (mResolveParams.mLocalId != -1))
					mParser.SetAutocomplete(mCursorPos);
				else
					mParser.SetAutocomplete(-1);
            }
			else
			{
                mParser = new BfParser(null);
				mOwnsParser = true;
			}

            mPassInstance = bfSystem.CreatePassInstance();
            mResolvePassData = mParser.CreateResolvePassData(((mKind == Kind.Rename) || (mKind == Kind.FindAllReferences)) ? ResolveType.RenameSymbol : ResolveType.ShowFileSymbolReferences);
            var resolveParams = mResolveParams;
            if (resolveParams != null)
            {                
                if (resolveParams.mTypeDef != null)
                    mResolvePassData.SetSymbolReferenceTypeDef(resolveParams.mTypeDef);
                if (resolveParams.mFieldIdx != -1)
                    mResolvePassData.SetSymbolReferenceFieldIdx(resolveParams.mFieldIdx);
                if (resolveParams.mMethodIdx != -1)
                    mResolvePassData.SetSymbolReferenceMethodIdx(resolveParams.mMethodIdx);
                if (resolveParams.mPropertyIdx != -1)
                    mResolvePassData.SetSymbolReferencePropertyIdx(resolveParams.mPropertyIdx);
                if (resolveParams.mLocalId != -1)
                    mResolvePassData.SetLocalId(resolveParams.mLocalId);
				if (resolveParams.mTypeGenericParamIdx != -1)
					mResolvePassData.SetTypeGenericParamIdx(resolveParams.mTypeGenericParamIdx);
				if (resolveParams.mMethodGenericParamIdx != -1)
					mResolvePassData.SetMethodGenericParamIdx(resolveParams.mMethodGenericParamIdx);
				if (resolveParams.mNamespace != null)
					mResolvePassData.SetSymbolReferenceNamespace(resolveParams.mNamespace);
            }

			mDoLock = mKind == Kind.Rename;

			Debug.Assert(mModifiedParsers == null);
			mModifiedParsers = new String();

			bool doBackground = true;

			if (doBackground)
			{
				bfCompiler.DoBackground(new => DoWork, new => FinishStartingWork);
			}
			else
			{
				//TODO: This never locked for find all references.  Why not?
				bfCompiler.GetSymbolReferences(mPassInstance, mResolvePassData, mModifiedParsers);
				FinishStartingWork();
			}
        }

		void FinishStartingWork()
		{
			var bfSystem = IDEApp.sApp.mBfResolveSystem;

			if (mDoLock)
				bfSystem.Lock(2);

			Debug.Assert(!mUsingResolvePassData);
			Debug.Assert(mStartingWork);
			mStartingWork = false;

			mUpdatingProjectSources = new List<ReplaceSymbolData>();
			for (var parserDataStr in mModifiedParsers.Split('\n'))
			{
			    if (parserDataStr == "")
			        return;
			    var parserData = parserDataStr.Split('\t');
			    //var projectSource = IDEApp.sApp.FindProjectSourceItem(parserData[0]);
				var filePath = parserData.GetNext().Get();

				if ((mKind == .ShowFileReferences) && (!Path.Equals(filePath, mParser.mFileName)))
				{
					//TODO: Assert?!
					continue;
				}

			    ReplaceSymbolData replaceSymbolData = new ReplaceSymbolData();
				var editData = gApp.GetEditData(scope String(filePath));
				replaceSymbolData.mFileEditData = editData;
			    //replaceSymbolData.mProjectSource = projectSource;

				if (mKind == .Rename)
				{
					replaceSymbolData.mIsLocked = editData.IsLocked();
				}

				var spanData = parserData.GetNext().Get().Split(' ');
				int count = 0;
				while (true)
				{
					if (spanData.GetNext() case .Err)
						break;
					count++;
				}
				spanData.Reset();
				count /= 2;

				replaceSymbolData.mSpans.Capacity = count;
				for (int i < count)
				{
				    ReplaceSpan replaceSpan;
				    replaceSpan.mSpanStart = int32.Parse(spanData.GetNext().Get());
				    replaceSpan.mSpanLength = int32.Parse(spanData.GetNext().Get());
					if (mOverrideSpanLengths)
						replaceSpan.mSpanLength = (.)mReplaceStr.Length;
				    replaceSymbolData.mSpans.Add(replaceSpan);
				}

			    replaceSymbolData.mSpans.Sort(scope (a, b) => a.mSpanStart - b.mSpanStart);
			    for (int32 i = 1; i < replaceSymbolData.mSpans.Count; i++)
			    {
			        if (replaceSymbolData.mSpans[i - 1].mSpanStart == replaceSymbolData.mSpans[i].mSpanStart)
			        {
			            replaceSymbolData.mSpans.RemoveAt(i);
			            i--;
			        }
			    }

			    mUpdatingProjectSources.Add(replaceSymbolData);
			}

			if (mKind == Kind.FindAllReferences)
			{
				PrintAllReferences();
				Close();
				return;
			}

			UpdateText();
		}

		public void EnsureWorkStarted()
		{
			if (mAwaitingGetSymbolInfo)
			{
				WaitForResolvedAll();
				if (!CheckGetSymbolInfo(false))
					return;
				mSourceViewPanel.[Friend]ProcessDeferredResolveResults(-1);
				StartWork();
			}

			if (!mStartingWork)
				return;

			var bfCompiler = IDEApp.sApp.mBfResolveCompiler;
			bfCompiler.WaitForBackground();
			Debug.Assert(!mStartingWork);
		}

        void UpdateText()
        {
            if (mUpdatingProjectSources == null)
                return;

            mIgnoreTextChanges = true;
            
			String prevReplaceStr = scope String();
			prevReplaceStr.Set(mReplaceStr);

            int32 strLenDiff = (int32)(mNewReplaceStr.Length - mReplaceStr.Length);
            mReplaceStr.Set(mNewReplaceStr);

			var activeSourceEditWidgetContent = (SourceEditWidgetContent)mSourceViewPanel.mEditWidget.Content;

            String newStr = mReplaceStr;
            bool didUndo = false;

			if (mKind == .Rename)
			{
				if (!mRenameHadChange)
				{
					if (newStr != mOrigReplaceStr)
						mRenameHadChange = true;
				}
			}

			GlobalUndoData globalUndoData = null;
			if (mKind == Kind.Rename)
			    globalUndoData = IDEApp.sApp.mGlobalUndoManager.CreateUndoData();

            List<int32> cursorPositions = scope List<int32>();
            for (var replaceSymbolData in mUpdatingProjectSources)
            {
                //var editData = IDEApp.sApp.GetEditData(replaceSymbolData.mProjectSource, true);
				var editData = replaceSymbolData.mFileEditData;
				if (editData.mEditWidget == null)
				{
					cursorPositions.Add(-1);
					continue;
				}	
                var sourceEditWidgetContent = (SourceEditWidgetContent)editData.mEditWidget.Content;
                cursorPositions.Add((int32)sourceEditWidgetContent.CursorTextPos);
            }

			var prevSelection = activeSourceEditWidgetContent.mSelection;

            for (int sourceIdx = 0; sourceIdx < mUpdatingProjectSources.Count; sourceIdx++)            
            {
                var replaceSymbolData = mUpdatingProjectSources[sourceIdx];
				if (replaceSymbolData.mIsLocked)
					continue;

				var editData = replaceSymbolData.mFileEditData;
				if (editData.mEditWidget == null)
					continue;

				if ((mKind == Kind.Rename) && (mRenameHadChange))
				{
					for (var projectSource in editData.mProjectSources)
					{
						projectSource.HasChangedSinceLastCompile = true;
					}
				}

                int32 cursorPos = cursorPositions[sourceIdx];

                var editWidgetContent = editData.mEditWidget.Content;

                if ((mUpdateTextCount != 0) && (!didUndo) && (mKind == Kind.Rename))
                {
                    while (true)
                    {
                        bool isGlobalBatchEnd = false;

                        var undoAction = editWidgetContent.mData.mUndoManager.GetLastUndoAction();
                        if (undoAction is UndoBatchEnd)
                        {
                            var undoBatchEnd = (UndoBatchEnd)undoAction;
                            isGlobalBatchEnd = undoBatchEnd.Name.StartsWith("#");                            
                        }
                        bool success = editWidgetContent.mData.mUndoManager.Undo();
                        Debug.Assert(success);
                        if (!success)
                            break;
                        if (isGlobalBatchEnd)
                            break;
                    }
                    didUndo = true;
                }

                SourceEditBatchHelper sourceEditBatchHelper = null;
                if (globalUndoData != null)
                {
                    globalUndoData.mFileEditDatas.Add(editData);
                    sourceEditBatchHelper = scope:: SourceEditBatchHelper(editData.mEditWidget, "#renameSymbol", new GlobalUndoAction(editData, globalUndoData));
                }

                int32 idxOffset = 0;
                for (int32 i = 0; i < replaceSymbolData.mSpans.Count; i++)
                {
                    int32 spanStart = replaceSymbolData.mSpans[i].mSpanStart + idxOffset;
                    int32 spanLen = replaceSymbolData.mSpans[i].mSpanLength;
                    
                    if ((mKind == Kind.Rename) &&
						((mRenameHadChange) || (editWidgetContent == activeSourceEditWidgetContent)))
                    {
                        editWidgetContent.CursorTextPos = spanStart;
                        var deleteCharAction = new EditWidgetContent.DeleteCharAction(editWidgetContent, 0, spanLen);
                        deleteCharAction.mMoveCursor = false;
                        editWidgetContent.mData.mUndoManager.Add(deleteCharAction);

                        editWidgetContent.PhysDeleteChars(0, spanLen);

                        editWidgetContent.CursorTextPos = spanStart;
                        var insertTextAction = new EditWidgetContent.InsertTextAction(editWidgetContent, newStr, .None);
                        insertTextAction.mMoveCursor = false;
                        editWidgetContent.mData.mUndoManager.Add(insertTextAction);
                        editWidgetContent.PhysInsertAtCursor(newStr, false);

                        if (spanStart <= cursorPos)
                            cursorPos += strLenDiff;
                    }                    

                    for (int32 attrIdx = spanStart; attrIdx < spanStart + newStr.Length; attrIdx++)
                    {
                        editWidgetContent.mData.mText[attrIdx].mDisplayFlags |= (uint8)(SourceElementFlags.SymbolReference);
                    }

					if (mKind == Kind.Rename)
                    	idxOffset += (int32)newStr.Length - spanLen;
                }
                
                if (sourceEditBatchHelper != null)
                    sourceEditBatchHelper.Finish();
                
				// Not a perfect check - theoretically the focus could have changed since renaming is delayed by a tick, not a huge issue...
                if ((!mDidRevert) && (editWidgetContent.mEditWidget.mHasFocus) && (mKind == Kind.Rename))
				{
                    editWidgetContent.CursorTextPos = (int32)Math.Max(0, cursorPos - strLenDiff);
				}
            }

            if ((mUpdateTextCount == 0) && (mKind == Kind.Rename))
            {
				activeSourceEditWidgetContent.mSelection = prevSelection;
            }

            mUpdateTextCount++;
            mIgnoreTextChanges = false;
            mDidRevert = false;
        }

		void StopWork()
		{
			var bfCompiler = IDEApp.sApp.mBfResolveCompiler;
			while (mStartingWork)
			{
				bfCompiler.CancelBackground();
			}
		}

        void Dispose()
        {
			mSourceViewPanel.CancelResolve(.GetSymbolInfo);
            if (mPassInstance != null)
            {
				StopWork();

                var bfSystem = IDEApp.sApp.mBfResolveSystem;
				if (mDoLock)
                	bfSystem.Unlock();

				Debug.Assert(!mUsingResolvePassData);
                delete mPassInstance;
                mPassInstance = null;
                delete mResolvePassData;
                mResolvePassData = null;

				if (mUpdatingProjectSources != null)
				{
					if ((mUpdatingProjectSources.IsEmpty) && (mSourceViewPanel.mEditData != null))
					{
						// If we have an error then won't necessarily even include ourselves, so add this here so we clear out the initial selection
						ReplaceSymbolData replaceSymbolData = new ReplaceSymbolData();
						replaceSymbolData.mFileEditData = mSourceViewPanel.mEditData;
						mUpdatingProjectSources.Add(replaceSymbolData);
					}

	                for (var replaceSymbolData in mUpdatingProjectSources)
	                {
						var editData = replaceSymbolData.mFileEditData;
						if (editData.mEditWidget == null)
							continue;
	                    var editWidgetContent = editData.mEditWidget.Content;
	                    for (int32 i = 0; i < editWidgetContent.mData.mTextLength; i++)
	                    {
	                        editWidgetContent.mData.mText[i].mDisplayFlags &= 0xFF ^ (uint8)(SourceElementFlags.SymbolReference);
	                    }

						using (gApp.mMonitor.Enter())
							editData.SetSavedData(null, IdSpan());
						var app = IDEApp.sApp;
						if ((mKind == Kind.Rename) && (IDEApp.IsBeefFile(editData.mFilePath)))
						{
							for (var projectSource in editData.mProjectSources)
								app.mBfResolveCompiler.QueueProjectSource(projectSource, .None, false);
							app.mBfResolveCompiler.QueueDeferredResolveAll();
						}
	                }
				}
            }
        }
        
        void RenameSymbolSubmit(bool isFinal)
        {
            //var editWidgetContent = mSourceViewPanel.mEditWidget;
            
            UpdateText();
        }

        public void Cancel()
        {            
            if ((mUpdatingProjectSources != null) && (mKind == Kind.Rename))
            {
                for (var replaceSymbolData in mUpdatingProjectSources)
                {
					if (replaceSymbolData.mIsLocked)
						continue;

					var editData = replaceSymbolData.mFileEditData;
                    var editWidgetContent = editData.mEditWidget.Content;

                    while (true)
                    {
                        bool isGlobalBatchEnd = false;

                        var undoAction = editWidgetContent.mData.mUndoManager.GetLastUndoAction();
                        if (undoAction is UndoBatchEnd)
                        {
                            var undoBatchEnd = (UndoBatchEnd)undoAction;
                            isGlobalBatchEnd = undoBatchEnd.Name.StartsWith("#");
                        }
                        bool success = editWidgetContent.mData.mUndoManager.Undo();
                        Debug.Assert(success);
                        if (!success)
                            break;
                        if (isGlobalBatchEnd)
                            break;
                    }

                    break;
                }
            }

            Close();
        }

        public override void Update()
        {
            base.Update();

			if (mKind == .GoToDefinition)
			{
				if (gApp.mBfResolveCompiler.HasResolvedAll())
				{
					Close();
					gApp.GoToDefinition(true);
				}

				if (mSourceViewPanel.EditWidget.Content.CursorTextPos != mCursorPos)
				{
					Close();
				}

				return;
			}

			if (mAwaitingGetSymbolInfo)
			{
				if (!CheckGetSymbolInfo(false))
					return;
			}

			if (mStartingWork)
				return;

            /*if (mReplaceStr == "")
            {
                //Cancel();
                //return;
                mReplaceStr = " ";
            }
            else*/ if (mTextChanged)
            {
                mTextChanged = false;
                UpdateText();
            }

            //var bfSystem = IDEApp.sApp.mBfResolveSystem;
            var bfCompiler = IDEApp.sApp.mBfResolveCompiler;

			if ((!mStartedWork) && (bfCompiler != null))
            {
				bool hasWorkLeft = bfCompiler.IsPerformingBackgroundOperation();
				if (mSourceViewPanel.[Friend]mWantsFullClassify)
					hasWorkLeft = true;
				if (mSourceViewPanel.HasDeferredResolveResults())
				{
					hasWorkLeft = true;
				}
                if (!hasWorkLeft)
                {
                    StartWork();
                }
            }
        }

		public override bool Contains(float x, float y)
		{
			if (mKind == Kind.ShowFileReferences)
				return false;
			return base.Contains(x, y);
		}

        public void Close()
        {
			if (mClosed)
				return;
			mClosed = true;

			mSourceViewPanel.CancelResolve(.GetSymbolInfo);
			if (mBackgroundKind != .None)
			{
				gApp.mBfResolveCompiler.CancelBackground();
				Debug.Assert(mBackgroundKind == .None);
			}

            if (mParent != null)
                RemoveSelf();
			if (mKind == .Rename)
				mSourceViewPanel.QueueFullRefresh(false);
            mSourceViewPanel.mRenameSymbolDialog = null;
            IDEApp.sApp.mSymbolReferenceHelper = null;
            Dispose();
			BFApp.sApp.DeferDelete(this);
        }

        public override void Draw(Graphics g)
        {
            if (mKind == Kind.ShowFileReferences)
                return;

			int symCount = 0;
			int readOnlyRefCount = 0;
			int lockedFileCount = 0;
			if (mUpdatingProjectSources != null)
			{
			    for (var replaceSymbolData in mUpdatingProjectSources)
			    {
			        symCount += replaceSymbolData.mSpans.Count;
					if (replaceSymbolData.mIsLocked)
					{
						lockedFileCount++;
						readOnlyRefCount += replaceSymbolData.mSpans.Count;
					}
			    }
			}

			float boxHeight = mHeight - GS!(8);

			if (lockedFileCount > 0)
			{
				boxHeight += GS!(14);
			}

            base.Draw(g);
            using (g.PushColor((lockedFileCount == 0) ? 0xFFFFFFFF : 0xFFF0B0B0))
                g.DrawBox(DarkTheme.sDarkTheme.GetImage(DarkTheme.ImageIdx.Menu), 0, 0, mWidth - GS!(8), boxHeight);

            using (g.PushColor(0xFFE0E0E0))
            {
                g.SetFont(DarkTheme.sDarkTheme.mSmallBoldFont);
				float border = GS!(8);

				String drawStr = null;
				if (!mOrigReplaceStr.IsEmpty)
				{
					var formatStr = mKind == .FindAllReferences ? "Finding '{0}'" : "Renaming '{0}'";
					drawStr = scope:: String()..AppendF(formatStr, mOrigReplaceStr);
				}
				else
				{
					switch (mKind)
					{
					case .FindAllReferences, .GoToDefinition:
						drawStr = "Finding...";
					case .Rename:
						drawStr = "Renaming...";
					default:
					}
				}

				if (drawStr != null)
					g.DrawString(drawStr, border, GS!(5), FontAlign.Centered, mWidth - border * 2 - GS!(8), FontOverflowMode.Ellipsis); 

                if (mUpdatingProjectSources != null)
                {
                    g.SetFont(DarkTheme.sDarkTheme.mSmallFont);
					var drawString = scope String();
					drawString.AppendF("{0} {1} in {2} {3}",
						symCount, (symCount == 1) ? "reference" : "references",
						mUpdatingProjectSources.Count, (mUpdatingProjectSources.Count == 1) ? "file" : "files");
                    g.DrawString(drawString, GS!(8), GS!(22), FontAlign.Centered, mWidth - GS!(8) - GS!(16));

					if (lockedFileCount > 0)
					{
						g.SetFont(DarkTheme.sDarkTheme.mSmallBoldFont);
						using (g.PushColor(((mUpdateCnt > 200) || (mUpdateCnt / 10 % 2 == 0)) ? 0xFFFF7070 : 0xFFFFB0B0))
							g.DrawString(scope String()..AppendF("{} {} LOCKED!", lockedFileCount, (lockedFileCount == 1) ? "FILE" : "FILES"),
								GS!(8), GS!(38), FontAlign.Centered, mWidth - GS!(8) - GS!(16));
					}
                }
            }

            if ((mUpdatingProjectSources == null) && (mUpdateCnt > 30))            
                IDEUtils.DrawWait(g, mWidth / 2, mHeight / 2 + 4, mUpdateCnt);
        }

        public void SourcePreInsertText(SourceEditWidgetContent sourceEditWidgetContent, int index, String text)
        {
            if (mIgnoreTextChanges)
                return;

			if (mStartingWork)
				return;

			bool hadSel = false;
			if ((mTextChanged) && (sourceEditWidgetContent.HasSelection()))
			{
				hadSel = true;
			}

            if ((mNewReplaceStr == "") && (index == mLastDeletedIdx))
            {
                mNewReplaceStr.Set(text);
                mSkipNextUpdate = true;
                mTextChanged = true;
                return;
            }

            // Close if insert is outside match area
            if ((index > 0) && (index < sourceEditWidgetContent.mData.mTextLength) &&
                ((sourceEditWidgetContent.mData.mText[index - 1].mDisplayFlags & (uint8)(SourceElementFlags.SymbolReference)) == 0) &&
                ((sourceEditWidgetContent.mData.mText[index].mDisplayFlags & (uint8)(SourceElementFlags.SymbolReference)) == 0))
            {
                Close();
            }
        }

        public void SourcePreRemoveText(SourceEditWidgetContent sourceEditWidgetContent, int index, int length)
        {
            if (mIgnoreTextChanges)
                return;
			if (mStartingWork)
				return;

            // Close if any char8 isn't within the match area
            bool isValid = true;
            for (int i = index; i < index + length; i++)
            {
                if ((sourceEditWidgetContent.mData.mText[i].mDisplayFlags & (uint8)(SourceElementFlags.SymbolReference)) == 0)
                    isValid = false;
            }

            mLastDeletedIdx = (int32)index;

            if (!isValid)
                Close();
        }

        public void SourceUpdateText(SourceEditWidgetContent sourceEditWidgetContent, int index)
        {
            if (mKind == Kind.ShowFileReferences)
            {
                Close();
                return;
            }

            if (mSkipNextUpdate)
            {
                mSkipNextUpdate = false;
                return;
            }

            if (mIgnoreTextChanges)
                return;
            
            int left = index;
            while (left > 0)
            {
                if ((sourceEditWidgetContent.mData.mText[left - 1].mDisplayFlags & (uint8)(SourceElementFlags.SymbolReference)) == 0)
                    break;
                left--;
            }

            int right = index;
            while (right < sourceEditWidgetContent.mData.mTextLength)
            {
                if ((sourceEditWidgetContent.mData.mText[right].mDisplayFlags & (uint8)(SourceElementFlags.SymbolReference)) == 0)
                    break;
                right++;
            }

            mNewReplaceStr.Clear();
            sourceEditWidgetContent.ExtractString(left, right - left, mNewReplaceStr);
            mTextChanged = true;
        }

        public void Revert()
        {
            mDidRevert = true;
            if (mNewReplaceStr == mOrigReplaceStr)
            {
                Cancel();
            }
            else
            {
                mNewReplaceStr.Set(mOrigReplaceStr);
                mTextChanged = true;
            }
        }
    }
}
