/****************************************************************************
**
** Copyright (C) 2018 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the Qt VS Tools.
**
** $QT_BEGIN_LICENSE:GPL-EXCEPT$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/

using System;
using System.IO;
using Microsoft.VisualStudio;
using Microsoft.VisualStudio.Debugger.Interop;

namespace QtVsTools.Qml.Debug.AD7
{
    sealed partial class CodeContext :

        IDebugDocumentContext2, // "This interface represents a position in a source file document."

        IDebugCodeContext2,     // "This interface represents the starting position of a code
        IDebugCodeContext100    //  instruction. For most run-time architectures today, a code
                                //  context can be thought of as an address in a program's execution
                                //  stream."
    {
        public QmlEngine Engine { get; private set; }
        public Program Program { get; private set; }

        public string FilePath { get; private set; }
        public uint FileLine { get; private set; }

        public enum Language { QML, JavaScript, Other }
        public Language FileType
        {
            get
            {
                var ext = Path.GetExtension(FilePath);
                if (string.Equals(ext, ".qml", StringComparison.InvariantCultureIgnoreCase))
                    return Language.QML;
                if (string.Equals(ext, ".js", StringComparison.InvariantCultureIgnoreCase))
                    return Language.JavaScript;
                return Language.Other;
            }
        }

        public static CodeContext Create(
            QmlEngine engine, Program program,
            string filePath, uint fileLine)
        {
            return new CodeContext()
            {
                Engine = engine,
                Program = program,
                FilePath = filePath,
                FileLine = fileLine
            };
        }

        private CodeContext()
        { }

        class CodeContextInfo : InfoHelper<CodeContextInfo>
        {
            public string Address { get; set; }
        }

        CodeContextInfo Info
        {
            get
            {
                return new CodeContextInfo
                {
                    Address = FileLine.ToString()
                };
            }
        }

        static readonly CodeContextInfo.Mapping MappingToCONTEXT_INFO =

        #region //////////////////// CONTEXT_INFO <-- CodeContextInfo /////////////////////////////
            // r: Ref<CONTEXT_INFO>
            // f: enum_CONTEXT_INFO_FIELDS
            // i: CodeContextInfo
            // v: value of i.<<property>>

            new CodeContextInfo.Mapping<CONTEXT_INFO, enum_CONTEXT_INFO_FIELDS>
                ((r, f) => r.s.dwFields |= f)
            {
                { enum_CONTEXT_INFO_FIELDS.CIF_ADDRESS,
                    (r, v) => r.s.bstrAddress = v, i => i.Address }
            };

        #endregion //////////////////// CONTEXT_INFO <-- CodeContextInfo //////////////////////////


        public int /*IDebugCodeContext2*/ GetInfo(
            enum_CONTEXT_INFO_FIELDS dwFields,
            CONTEXT_INFO[] pinfo)
        {
            Info.Map(MappingToCONTEXT_INFO, dwFields, out pinfo[0]);
            return VSConstants.S_OK;
        }

        public int /*IDebugCodeContext2*/ Compare(
            enum_CONTEXT_COMPARE Compare,
            IDebugMemoryContext2[] rgpMemoryContextSet,
            uint dwMemoryContextSetLen,
            out uint pdwMemoryContext)
        {
            pdwMemoryContext = uint.MaxValue;
            if (Compare != enum_CONTEXT_COMPARE.CONTEXT_EQUAL)
                return VSConstants.E_NOTIMPL;

            for (uint i = 0; i < dwMemoryContextSetLen; ++i) {
                var that = rgpMemoryContextSet[i] as CodeContext;
                if (that == null)
                    continue;
                if (this.Engine != that.Engine)
                    continue;
                if (this.Program != that.Program)
                    continue;
                if (!string.Equals(
                    Path.GetFullPath(this.FilePath), Path.GetFullPath(that.FilePath),
                    StringComparison.InvariantCultureIgnoreCase)) {
                    continue;
                }
                if (this.FileLine != that.FileLine)
                    continue;

                pdwMemoryContext = i;
                return VSConstants.S_OK;
            }

            return VSConstants.S_FALSE;
        }

        int IDebugDocumentContext2.GetName(enum_GETNAME_TYPE gnType, out string pbstrFileName)
        {
            pbstrFileName = FilePath;
            return VSConstants.S_OK;
        }

        int IDebugDocumentContext2.GetStatementRange(
            TEXT_POSITION[] pBegPosition,
            TEXT_POSITION[] pEndPosition)
        {
            pBegPosition[0].dwLine = FileLine;
            pBegPosition[0].dwColumn = 0;
            pEndPosition[0].dwLine = FileLine;
            pEndPosition[0].dwColumn = 0;
            return VSConstants.S_OK;
        }

        int IDebugDocumentContext2.EnumCodeContexts(out IEnumDebugCodeContexts2 ppEnumCodeCxts)
        {
            ppEnumCodeCxts = CodeContextEnum.Create(this);
            return VSConstants.S_OK;
        }

        int IDebugDocumentContext2.GetLanguageInfo(
            ref string pbstrLanguage,
            ref Guid pguidLanguage)
        {
            pbstrLanguage = "C++";
            pguidLanguage = NativeEngine.IdLanguageCpp;
            return VSConstants.S_OK;
        }

        int IDebugCodeContext2.GetLanguageInfo(ref string pbstrLanguage, ref Guid pguidLanguage)
        {
            return (this as IDebugDocumentContext2)
                .GetLanguageInfo(ref pbstrLanguage, ref pguidLanguage);
        }

        int IDebugCodeContext2.GetDocumentContext(out IDebugDocumentContext2 ppSrcCxt)
        {
            ppSrcCxt = this;
            return VSConstants.S_OK;
        }

        int IDebugCodeContext100.GetProgram(out IDebugProgram2 ppProgram)
        {
            ppProgram = Program;
            return VSConstants.S_OK;
        }
    }
}