/****************************************************************************
**
** Copyright (C) 2016 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 Microsoft.Internal.VisualStudio.PlatformUI;
using Microsoft.VisualStudio.OLE.Interop;
using Microsoft.VisualStudio.Shell;
using Microsoft.VisualStudio.Shell.Interop;
using Microsoft.VisualStudio.TemplateWizard;
using QtVsTools.Core;
using QtVsTools.VisualStudio;
using System.Collections.Generic;
using System.IO;
using System.Linq;

namespace QtVsTools.Wizards.ClassWizard
{
    public class CoreClassWizard : IClassWizard
    {
        public WizardResult Run(EnvDTE.DTE dte, string name, string location)
        {
            ThreadHelper.ThrowIfNotOnUIThread();

            var serviceProvider = new ServiceProvider(dte as IServiceProvider);
            var iVsUIShell = VsServiceProvider.GetService<SVsUIShell, IVsUIShell>();

            iVsUIShell.EnableModeless(0);

            try {
                System.IntPtr hwnd;
                iVsUIShell.GetDialogOwnerHwnd(out hwnd);

                try {
                    if (string.IsNullOrEmpty(name))
                        name = @"QtClass";

                    data.ClassName = name;
                    data.BaseClass = @"QObject";
                    data.ConstructorSignature = "QObject *parent";
                    data.InsertQObjectMacro = true;
                    data.ClassHeaderFile = name + @".h";
                    data.ClassSourceFile = name + @".cpp";

                    var wizard = new WizardWindow(new List<WizardPage> {
                        new WizardIntroPage {
                            Data = data,
                            Header = @"Welcome to the Qt Class Wizard",
                            Message = @"This wizard will add a new Qt class to your project. The "
                                + @"wizard creates a .h and .cpp file." + System.Environment.NewLine
                                + System.Environment.NewLine + "To continue, click Next.",
                            PreviousButtonEnabled = false,
                            NextButtonEnabled = true,
                            FinishButtonEnabled = false,
                            CancelButtonEnabled = true
                        },
                        new CoreClassPage {
                            Data = data,
                            Header = @"Welcome to the Qt Class Wizard",
                            Message = @"This wizard will add a new Qt class to your project. The "
                                + @"wizard creates a .h and .cpp file.",
                            PreviousButtonEnabled = true,
                            NextButtonEnabled = false,
                            FinishButtonEnabled = true,
                            CancelButtonEnabled = true
                        }
                    })
                    {
                        Title = @"Qt Class Wizard"
                    };
                    WindowHelper.ShowModal(wizard, hwnd);
                    if (!wizard.DialogResult.HasValue || !wizard.DialogResult.Value)
                        throw new System.Exception("Unexpected wizard return value.");

                    var array = data.ClassName.Split(new[] { "::" }, System
                        .StringSplitOptions.RemoveEmptyEntries);
                    data.ClassName = array.LastOrDefault();

                    string nsBegin = string.Empty, nsEnd = string.Empty;
                    for (var i = 0; i < array.Length - 1; ++i) {
                        nsBegin += "namespace " + array[i] + " {\r\n";
                        nsEnd = "} // namespace " + array[i] + "\r\n" + nsEnd;
                    }

                    var pro = HelperFunctions.GetSelectedQtProject(dte);
                    if (pro == null)
                        throw new QtVSException("Can't find a selected project");

                    var qtProject = QtProject.Create(pro);

                    var hppFile = AddProjectItemHpp(location);
                    ReplaceNamespaceToken(hppFile, nsBegin, nsEnd);

                    qtProject.AdjustWhitespace(hppFile);
                    qtProject.AddFileToProject(hppFile, Filters.HeaderFiles());
                    VsShellUtilities.OpenDocument(serviceProvider, hppFile);

                    var pch = string.Empty;
                    if (qtProject.UsesPrecompiledHeaders())
                        pch = qtProject.GetPrecompiledHeaderThrough();

                    var cppFile = AddProjectItemCpp(location, pch);
                    ReplaceNamespaceToken(cppFile, nsBegin, nsEnd);

                    qtProject.AdjustWhitespace(cppFile);
                    qtProject.AddFileToProject(cppFile, Filters.SourceFiles());
                    VsShellUtilities.OpenDocument(serviceProvider, cppFile);

                } catch (QtVSException exception) {
                    Messages.DisplayErrorMessage(exception.Message);
                    throw; // re-throw, but keep the original exception stack intact
                }
            } catch {
                throw new WizardBackoutException();
            } finally {
                iVsUIShell.EnableModeless(1);
            }

            return WizardResult.Finished;
        }

        private readonly WizardData data = new WizardData
        {
            DefaultModules = new List<string> {
                @"QtCore"
            }
        };

        private static void ReplaceNamespaceToken(string file, string nsBegin, string nsEnd)
        {
            if (!string.IsNullOrEmpty(nsBegin))
                nsBegin += System.Environment.NewLine;
            QtProject.ReplaceTokenInFile(file, "%NAMESPACE_BEGIN%", nsBegin);

            if (!string.IsNullOrEmpty(nsEnd))
                nsEnd = System.Environment.NewLine + nsEnd;
            QtProject.ReplaceTokenInFile(file, "%NAMESPACE_END%", nsEnd);
        }

        private string AddProjectItemHpp(string location)
        {
            var hppFile = Path.GetTempFileName();
            using (var tmp = new StreamWriter(hppFile)) {
                tmp.Write("#pragma once\r\n\r\n"
                    + "%BASECLASSINCLUDE%%NAMESPACE_BEGIN%class %CLASS%%BASEDECL%\r\n"
                    + "{%Q_OBJECT%\r\n"
                    + "public:\r\n"
                    + "    %CLASS%(%CTORSIG%);\r\n"
                    + "    ~%CLASS%();\r\n"
                    + "};\r\n"
                    + "%NAMESPACE_END%");
            }
            hppFile = QtProject.CopyFileToFolder(hppFile, location, data.ClassHeaderFile);

            QtProject.ReplaceTokenInFile(hppFile, "%CLASS%", data.ClassName);
            QtProject.ReplaceTokenInFile(hppFile, "%BASECLASS%", data.BaseClass);

            if (string.IsNullOrEmpty(data.BaseClass)) {
                QtProject.ReplaceTokenInFile(hppFile, "%BASEDECL%", string.Empty);
                QtProject.ReplaceTokenInFile(hppFile, "%BASECLASSINCLUDE%", string.Empty);
                QtProject.ReplaceTokenInFile(hppFile, "%Q_OBJECT%", string.Empty);
                QtProject.ReplaceTokenInFile(hppFile, "%CTORSIG%", string.Empty);
            } else {
                QtProject.ReplaceTokenInFile(hppFile, "%BASEDECL%", " : public "
                    + data.BaseClass);
                QtProject.ReplaceTokenInFile(hppFile, "%BASECLASSINCLUDE%", "#include <"
                    + data.BaseClass + ">\r\n\r\n");
                if (data.InsertQObjectMacro)
                    QtProject.ReplaceTokenInFile(hppFile, "%Q_OBJECT%", "\r\n\tQ_OBJECT\r\n");
                QtProject.ReplaceTokenInFile(hppFile, "%CTORSIG%", data.ConstructorSignature);
            }

            return hppFile;
        }

        private string AddProjectItemCpp(string location, string pch)
        {
            var cppFile = Path.GetTempFileName();
            using (var tmp = new StreamWriter(cppFile)) {
                tmp.Write("#include \"%INCLUDE%\"\r\n\r\n"
                    + "%NAMESPACE_BEGIN%%CLASS%::%CLASS%(%CTORSIG%)%BASECLASS%\r\n"
                    + "{\r\n}\r\n\r\n"
                    + "%CLASS%::~%CLASS%()\r\n"
                    + "{\r\n}\r\n"
                    + "%NAMESPACE_END%");
            }
            cppFile = QtProject.CopyFileToFolder(cppFile, location, data.ClassSourceFile);

            if (!string.IsNullOrEmpty(pch))
                QtProject.ReplaceTokenInFile(cppFile, "%INCLUDE%", pch + "\"\r\n#include \"%INCLUDE%");

            QtProject.ReplaceTokenInFile(cppFile, "%INCLUDE%", data.ClassHeaderFile);
            QtProject.ReplaceTokenInFile(cppFile, "%CLASS%", data.ClassName);

            QtProject.ReplaceTokenInFile(cppFile, "%BASECLASS%", string.IsNullOrEmpty(data
                .ConstructorSignature) ? "" : "\r\n    : " + data.BaseClass + "(parent)");
            QtProject.ReplaceTokenInFile(cppFile, "%CTORSIG%", data.ConstructorSignature);

            return cppFile;
        }
    }
}