using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.VisualStudio;
using Microsoft.VisualStudio.Debugger.Interop;
using System.Diagnostics;

namespace Cosmos.Debug.VSDebugEngine
{
    // This class implements IDebugThread2 which represents a thread running in a program.
    public class AD7Thread : IDebugThread2
    {
        readonly AD7Engine m_engine;
        //readonly DebuggedThread m_debuggedThread;
        //TODO: Can we safely change this?
        const string ThreadNameString = "Sample Engine Thread";
        private AD7Process mProcess;

        public AD7Thread(AD7Engine engine, AD7Process aProcess)//, DebuggedThread debuggedThread)
        {
            m_engine = engine;
            mProcess = aProcess;
        }      
        
        string GetCurrentLocation(bool fIncludeModuleName)
        {
            uint ip = 0;// GetThreadContext().eip;
            string location = m_engine.GetAddressDescription(ip);

            return location;
        }

        #region IDebugThread2 Members

        // Determines whether the next statement can be set to the given stack frame and code context.
        // The sample debug engine does not support set next statement, so S_FALSE is returned.
        int IDebugThread2.CanSetNextStatement(IDebugStackFrame2 stackFrame, IDebugCodeContext2 codeContext)
        {
            return VSConstants.S_FALSE; 
        }

        // Retrieves a list of the stack frames for this thread.
        // For the sample engine, enumerating the stack frames requires walking the callstack in the debuggee for this thread
        // and coverting that to an implementation of IEnumDebugFrameInfo2. 
        // Real engines will most likely want to cache this information to avoid recomputing it each time it is asked for,
        // and or construct it on demand instead of walking the entire stack.
        int IDebugThread2.EnumFrameInfo(enum_FRAMEINFO_FLAGS dwFieldSpec, uint nRadix, out IEnumDebugFrameInfo2 enumObject)
        {
            System.Diagnostics.Debug.WriteLine("AD7Thread.EnumFrameInfo");
            System.Diagnostics.Debug.WriteLine("\tdwFieldSpec = 0x" + dwFieldSpec.ToString("X"));
            System.Diagnostics.Debug.WriteLine("\tnRadix = 0x" + nRadix.ToString("X"));
            // Ask the lower-level to perform a stack walk on this thread
            //m_engine.DebuggedProcess.DoStackWalk(this.m_debuggedThread);
            enumObject = null;

            try
            {
                //System.Collections.Generic.List<X86ThreadContext> stackFrames = this.m_debuggedThread.StackFrames;
                //int numStackFrames = stackFrames.Count;
                FRAMEINFO[] frameInfoArray;

                //if (numStackFrames == 0)
                {
                    // failed to walk any frames. Only return the top frame.
                    frameInfoArray = new FRAMEINFO[1];
                    AD7StackFrame frame = new AD7StackFrame(m_engine, this, mProcess);
                    frame.SetFrameInfo((enum_FRAMEINFO_FLAGS)dwFieldSpec, out frameInfoArray[0]);
                }
                //else
                {
                    //frameInfoArray = new FRAMEINFO[numStackFrames];

                    //for (int i = 0; i < numStackFrames; i++)
                    {
                        //AD7StackFrame frame = new AD7StackFrame(m_engine, this, stackFrames[i]);
                        //frame.SetFrameInfo(dwFieldSpec, out frameInfoArray[i]);
                    }
                }

                enumObject = new AD7FrameInfoEnum(frameInfoArray);
                return VSConstants.S_OK;
            }
            //catch (ComponentException e)
            //{
            //    return e.HResult;
            //}
            catch (Exception e)
            {
                return EngineUtils.UnexpectedException(e);
            } 
        }

        // Get the name of the thread. For the sample engine, the name of the thread is always "Sample Engine Thread"
        int IDebugThread2.GetName(out string threadName)
        {
            threadName = ThreadNameString;
            return VSConstants.S_OK;
        }

        // Return the program that this thread belongs to.
        int IDebugThread2.GetProgram(out IDebugProgram2 program)
        {
            program = m_engine;
            return VSConstants.S_OK;
        }

        // Gets the system thread identifier.
        int IDebugThread2.GetThreadId(out uint threadId)
        {
            threadId = 0;// (uint)m_debuggedThread.Id;
            return VSConstants.S_OK;
        }

        // Gets properties that describe a thread.
        int IDebugThread2.GetThreadProperties(enum_THREADPROPERTY_FIELDS dwFields, THREADPROPERTIES[] propertiesArray)
        {
            try
            {
                THREADPROPERTIES props = new THREADPROPERTIES();

                if (dwFields.HasFlag(enum_THREADPROPERTY_FIELDS.TPF_ID))
                {
                    //props.dwThreadId = (uint)m_debuggedThread.Id;
                    props.dwFields |= enum_THREADPROPERTY_FIELDS.TPF_ID;
                }
                if (dwFields.HasFlag(enum_THREADPROPERTY_FIELDS.TPF_SUSPENDCOUNT)) 
                {
                    // sample debug engine doesn't support suspending threads
                    props.dwFields |= enum_THREADPROPERTY_FIELDS.TPF_SUSPENDCOUNT;
                }
                if (dwFields.HasFlag(enum_THREADPROPERTY_FIELDS.TPF_STATE)) 
                {
                    props.dwThreadState = (uint)enum_THREADSTATE.THREADSTATE_RUNNING;
                    props.dwFields |= enum_THREADPROPERTY_FIELDS.TPF_STATE;
                }
                if (dwFields.HasFlag(enum_THREADPROPERTY_FIELDS.TPF_PRIORITY)) 
                {
                    props.bstrPriority = "Normal";
                    props.dwFields |= enum_THREADPROPERTY_FIELDS.TPF_PRIORITY;
                }
                if (dwFields.HasFlag(enum_THREADPROPERTY_FIELDS.TPF_NAME))
                {
                    props.bstrName = ThreadNameString;
                    props.dwFields |= enum_THREADPROPERTY_FIELDS.TPF_NAME;
                }
                if (dwFields.HasFlag(enum_THREADPROPERTY_FIELDS.TPF_LOCATION))
                {
                    props.bstrLocation = GetCurrentLocation(true);
                    props.dwFields |= enum_THREADPROPERTY_FIELDS.TPF_LOCATION;
                }

                return VSConstants.S_OK;
            }
            //catch (ComponentException e)
            //{
            //    return e.HResult;
            //}
            catch (Exception e)
            {
                return EngineUtils.UnexpectedException(e);
            }
        }

        // Resume a thread.
        // This is called when the user chooses "Unfreeze" from the threads window when a thread has previously been frozen.
        int IDebugThread2.Resume(out uint suspendCount)
        {
            // The sample debug engine doesn't support suspending/resuming threads
            suspendCount = 0;
            return VSConstants.E_NOTIMPL;
        }

        // Sets the next statement to the given stack frame and code context.
        // The sample debug engine doesn't support set next statment
        int IDebugThread2.SetNextStatement(IDebugStackFrame2 stackFrame, IDebugCodeContext2 codeContext)
        {
            return VSConstants.E_NOTIMPL;
        }

        // suspend a thread.
        // This is called when the user chooses "Freeze" from the threads window
        int IDebugThread2.Suspend(out uint suspendCount)
        {
            // The sample debug engine doesn't support suspending/resuming threads
            suspendCount = 0;
            return VSConstants.E_NOTIMPL;
        }

        #endregion

        #region Uncalled interface methods
        // These methods are not currently called by the Visual Studio debugger, so they don't need to be implemented

        int IDebugThread2.GetLogicalThread(IDebugStackFrame2 stackFrame, out IDebugLogicalThread2 logicalThread)
        {
            System.Diagnostics.Debug.Fail("This function is not called by the debugger");

            logicalThread = null;
            return VSConstants.E_NOTIMPL;
        }

        int IDebugThread2.SetThreadName(string name)
        {
            System.Diagnostics.Debug.Fail("This function is not called by the debugger");

            return VSConstants.E_NOTIMPL;
        }

        #endregion
    }
}
