/* ****************************************************************************
 *
 * Copyright (c) Microsoft Corporation. 
 *
 * This source code is subject to terms and conditions of the Apache License, Version 2.0. A 
 * copy of the license can be found in the License.html file at the root of this distribution. If 
 * you cannot locate the Apache License, Version 2.0, please send an email to 
 * vspython@microsoft.com. By using this source code in any fashion, you are agreeing to be bound 
 * by the terms of the Apache License, Version 2.0.
 *
 * You must not remove this notice, or any other, from this software.
 *
 * ***************************************************************************/

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.IO;
using System.Runtime.InteropServices;
using Microsoft.VisualStudio;
using Microsoft.VisualStudio.OLE.Interop;
using Microsoft.VisualStudio.Shell;
using Microsoft.VisualStudio.Shell.Interop;
using VSLangProj;
using IOleServiceProvider = Microsoft.VisualStudio.OLE.Interop.IServiceProvider;

namespace Microsoft.VisualStudio.Project
{

    /// <summary>
    /// All public properties on Nodeproperties or derived classes are assumed to be used by Automation by default.
    /// Set this attribute to false on Properties that should not be visible for Automation.
    /// </summary>
    [AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = true)]
    public sealed class AutomationBrowsableAttribute : System.Attribute
    {
        public AutomationBrowsableAttribute(bool browsable)
        {
            this.browsable = browsable;
        }

        public bool Browsable
        {
            get
            {
                return this.browsable;
            }
        }

        private bool browsable;
    }

    /// <summary>
    /// To create your own localizable node properties, subclass this and add public properties
    /// decorated with your own localized display name, category and description attributes.
    /// </summary>
    [ComVisible(true)]
    public class NodeProperties : LocalizableProperties,
        ISpecifyPropertyPages,
        IVsGetCfgProvider,
        IVsSpecifyProjectDesignerPages,
        IVsBrowseObject
    {
        #region fields
        private HierarchyNode node;
        #endregion

        #region properties
        [Browsable(false)]
        [AutomationBrowsable(true)]
        public HierarchyNode Node
        {
            get { return this.node; }
        }

        [Browsable(false)]
        public HierarchyNode HierarchyNode {
            get { return this.node; }
        }


        /// <summary>
        /// Used by Property Pages Frame to set it's title bar. The Caption of the Hierarchy Node is returned.
        /// </summary>
        [Browsable(false)]
        [AutomationBrowsable(false)]
        public virtual string Name
        {
            get { return this.node.Caption; }
        }

        #endregion

        #region ctors
        public NodeProperties(HierarchyNode node)
        {
            VsUtilities.ArgumentNotNull("node", node);
            this.node = node;
        }
        #endregion

        #region ISpecifyPropertyPages methods
        public virtual void GetPages(CAUUID[] pages)
        {
            this.GetSettingsPages(pages, __VSHPROPID2.VSHPROPID_CfgPropertyPagesCLSIDList);
        }
        #endregion

        #region IVsSpecifyProjectDesignerPages
        /// <summary>
        /// Implementation of the IVsSpecifyProjectDesignerPages. It will retun the pages that are configuration independent.
        /// </summary>
        /// <param name="pages">The pages to return.</param>
        /// <returns></returns>
        public virtual int GetProjectDesignerPages(CAUUID[] pages)
        {
            this.GetSettingsPages(pages, __VSHPROPID2.VSHPROPID_PropertyPagesCLSIDList);
            return VSConstants.S_OK;
        }
        #endregion

        #region IVsGetCfgProvider methods
        public virtual int GetCfgProvider(out IVsCfgProvider p)
        {
            p = null;
            return VSConstants.E_NOTIMPL;
        }
        #endregion

        #region IVsBrowseObject methods
        /// <summary>
        /// Maps back to the hierarchy or project item object corresponding to the browse object.
        /// </summary>
        /// <param name="hier">Reference to the hierarchy object.</param>
        /// <param name="itemid">Reference to the project item.</param>
        /// <returns>If the method succeeds, it returns S_OK. If it fails, it returns an error code. </returns>
        public virtual int GetProjectItem(out IVsHierarchy hier, out uint itemid)
        {
            VsUtilities.CheckNotNull(node);

            hier = node.ProjectMgr.GetOuterInterface<IVsHierarchy>();
            itemid = this.node.ID;
            return VSConstants.S_OK;
        }
        #endregion

        #region overridden methods
        /// <summary>
        /// Get the Caption of the Hierarchy Node instance. If Caption is null or empty we delegate to base
        /// </summary>
        /// <returns>Caption of Hierarchy node instance</returns>
        public override string GetComponentName()
        {
            string caption = this.HierarchyNode.Caption;
            if (string.IsNullOrEmpty(caption))
            {
                return base.GetComponentName();
            }
            else
            {
                return caption;
            }
        }
        #endregion

        #region helper methods
        protected string GetProperty(string name, string def)
        {
            string a = this.HierarchyNode.ItemNode.GetMetadata(name);
            return (a == null) ? def : a;
        }

        protected void SetProperty(string name, string value)
        {
            this.HierarchyNode.ItemNode.SetMetadata(name, value);
        }

        /// <summary>
        /// Retrieves the common property pages. The NodeProperties is the BrowseObject and that will be called to support 
        /// configuration independent properties.
        /// </summary>
        /// <param name="pages">The pages to return.</param>
        private void GetSettingsPages(CAUUID[] pages, __VSHPROPID2 sourceProperty)
        {
            // We do not check whether the supportsProjectDesigner is set to false on the ProjectNode.
            // We rely that the caller knows what to call on us.
            VsUtilities.ArgumentNotNull("pages", pages);

            if (pages.Length == 0)
            {
                throw new ArgumentException(SR.GetString(SR.InvalidParameter, CultureInfo.CurrentUICulture), "pages");
            }

            // Only the project should show the property page the rest should show the project properties.
            if (this.node != null && (this.node is ProjectNode))
            {
                // Retrieve the list of guids from hierarchy properties.
                // Because a flavor could modify that list we must make sure we are calling the outer most implementation of IVsHierarchy
                string guidsList = String.Empty;
                IVsHierarchy hierarchy = HierarchyNode.ProjectMgr.GetOuterInterface<IVsHierarchy>();
                object variant = null;
                ErrorHandler.ThrowOnFailure(hierarchy.GetProperty(VSConstants.VSITEMID_ROOT, (int)sourceProperty, out variant));
                guidsList = (string)variant;

                Guid[] guids = VsUtilities.GuidsArrayFromSemicolonDelimitedStringOfGuids(guidsList);
                if (guids == null || guids.Length == 0)
                {
                    pages[0] = new CAUUID();
                    pages[0].cElems = 0;
                }
                else
                {
                    pages[0] = PackageUtilities.CreateCAUUIDFromGuidArray(guids);
                }
            }
            else
            {
                pages[0] = new CAUUID();
                pages[0].cElems = 0;
            }
        }
        #endregion

        #region ExtenderSupport
        [Browsable(false)]
        [SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "CATID")]
        public virtual string ExtenderCATID
        {
            get
            {
                Guid catid = this.HierarchyNode.ProjectMgr.GetCATIDForType(this.GetType());
                if (Guid.Empty.CompareTo(catid) == 0)
                {
                    return null;
                }
                return catid.ToString("B");
            }
        }

        [Browsable(false)]
        public object ExtenderNames()
        {
            EnvDTE.ObjectExtenders extenderService = (EnvDTE.ObjectExtenders)this.HierarchyNode.GetService(typeof(EnvDTE.ObjectExtenders));
            Debug.Assert(extenderService != null, "Could not get the ObjectExtenders object from the services exposed by this property object");
            VsUtilities.CheckNotNull(extenderService);

            return extenderService.GetExtenderNames(this.ExtenderCATID, this);
        }

        public object Extender(string extenderName)
        {
            EnvDTE.ObjectExtenders extenderService = (EnvDTE.ObjectExtenders)this.HierarchyNode.GetService(typeof(EnvDTE.ObjectExtenders));
            Debug.Assert(extenderService != null, "Could not get the ObjectExtenders object from the services exposed by this property object");
            VsUtilities.CheckNotNull(extenderService);
            return extenderService.GetExtender(this.ExtenderCATID, extenderName, this);
        }

        #endregion
    }

    [ComVisible(true)]
    public class FileNodeProperties : NodeProperties
    {
        #region properties


        [SRCategoryAttribute(SR.Misc)]
        [LocDisplayName(SR.FileName)]
        [SRDescriptionAttribute(SR.FileNameDescription)]
        public virtual string FileName
        {
            get
            {
                return this.HierarchyNode.Caption;
            }
            set
            {
                this.HierarchyNode.SetEditLabel(value);
            }
        }

        [SRCategoryAttribute(SR.Misc)]
        [LocDisplayName(SR.FullPath)]
        [SRDescriptionAttribute(SR.FullPathDescription)]
        public string FullPath
        {
            get
            {
                return this.HierarchyNode.Url;
            }
        }

        #region non-browsable properties - used for automation only

        [Browsable(false)]
        public string URL
        {
            get
            {
                return this.HierarchyNode.Url;
            }
        }

        [Browsable(false)]
        public string Extension
        {
            get
            {
                return Path.GetExtension(this.HierarchyNode.Caption);
            }
        }

        #endregion

        #endregion

        #region ctors
        public FileNodeProperties(HierarchyNode node)
            : base(node)
        {
        }
        #endregion

        public override string GetClassName()
        {
            return SR.GetString(SR.FileProperties, CultureInfo.CurrentUICulture);
        }
    }

    [ComVisible(true)]
    public class ExcludedFileNodeProperties : FileNodeProperties {
        public ExcludedFileNodeProperties(HierarchyNode node)
            : base(node)
        {
        }

        [SRCategoryAttribute(SR.Advanced)]
        [LocDisplayName(SR.BuildAction)]
        [SRDescriptionAttribute(SR.BuildActionDescription)]
        [TypeConverter(typeof(BuildActionTypeConverter))]
        public prjBuildAction BuildAction 
        {
            get 
            {
                return prjBuildAction.prjBuildActionNone;
            }
        }
    }

    [ComVisible(true)]
    public class IncludedFileNodeProperties : FileNodeProperties 
    {
        public IncludedFileNodeProperties(HierarchyNode node)
            : base(node)
        {
        }

        [SRCategoryAttribute(SR.Advanced)]
        [LocDisplayName(SR.BuildAction)]
        [SRDescriptionAttribute(SR.BuildActionDescription)]
        [TypeConverter(typeof(BuildActionTypeConverter))]
        public prjBuildAction BuildAction 
        {
            get 
            {
                return (prjBuildAction)BuildActionTypeConverter.Instance.ConvertFromString(HierarchyNode.ItemNode.ItemTypeName);
            }
            set 
            {
                this.HierarchyNode.ItemNode.ItemTypeName = BuildActionTypeConverter.Instance.ConvertToString(value);
            }
        }

        [SRCategoryAttribute(SR.Advanced)]
        [LocDisplayName(SR.Publish)]
        [SRDescriptionAttribute(SR.PublishDescription)]
        public bool Publish 
        {
            get 
            {
                var publish = this.HierarchyNode.ItemNode.GetMetadata("Publish");
                if (String.IsNullOrEmpty(publish)) 
                {
                    if (this.HierarchyNode.ItemNode.ItemTypeName == "Compile") 
                    {
                        return true;
                    }
                    return false;
                }
                return Convert.ToBoolean(publish);
            }
            set 
            {

                this.HierarchyNode.ItemNode.SetMetadata("Publish", value.ToString());
            }
        }

        [Browsable(false)]
        public string SourceControlStatus 
        {
            get 
            {
                // remove STATEICON_ and return rest of enum
                return HierarchyNode.StateIconIndex.ToString().Substring(10);
            }
        }

    }

    [ComVisible(true)]
    public class LinkFileNodeProperties : FileNodeProperties
    {
        public LinkFileNodeProperties(HierarchyNode node)
            : base(node)
        {

        }

        [SRCategoryAttribute(SR.Misc)]
        [LocDisplayName(SR.FileName)]
        [SRDescriptionAttribute(SR.FileNameDescription)]
        [ReadOnly(true)]
        public override string FileName
        {
            get
            {
                return this.HierarchyNode.Caption;
            }
            set
            {
                throw new InvalidOperationException();
            }
        }
    }


    [ComVisible(true)]
    public class DependentFileNodeProperties : NodeProperties
    {
        #region properties

        [SRCategoryAttribute(SR.Misc)]
        [LocDisplayName(SR.FileName)]
        [SRDescriptionAttribute(SR.FileNameDescription)]
        public virtual string FileName
        {
            get
            {
                return this.HierarchyNode.Caption;
            }
        }

        [SRCategoryAttribute(SR.Misc)]
        [LocDisplayName(SR.FullPath)]
        [SRDescriptionAttribute(SR.FullPathDescription)]
        public string FullPath
        {
            get
            {
                return this.HierarchyNode.Url;
            }
        }
        #endregion

        #region ctors
        public DependentFileNodeProperties(HierarchyNode node)
            : base(node)
        {
        }

        #endregion

        public override string GetClassName()
        {
            return SR.GetString(SR.FileProperties, CultureInfo.CurrentUICulture);
        }
    }

    class BuildActionTypeConverter : StringConverter
    {
        public static readonly BuildActionTypeConverter Instance = new BuildActionTypeConverter();

        public BuildActionTypeConverter()
        {
        }

        public override bool GetStandardValuesSupported(ITypeDescriptorContext context)
        {
            return true;
        }

        public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
        {
            if (sourceType == typeof(string))
            {
                return true;
            }
            return base.CanConvertFrom(context, sourceType);
        }

        public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
        {
            return base.CanConvertTo(context, destinationType);
        }

        public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType)
        {
            if (destinationType == typeof(string))
            {
                switch((prjBuildAction)value)  {
                    case prjBuildAction.prjBuildActionCompile:
                        return "Compile";
                    case prjBuildAction.prjBuildActionContent:
                        return "Content";
                    case prjBuildAction.prjBuildActionNone:
                        return "None";
                }
            }
            return base.ConvertTo(context, culture, value, destinationType);
        }

        public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
        {
            if (value is string)
            {
                string strVal = (string)value;
                if (strVal.Equals("Compile", StringComparison.OrdinalIgnoreCase)) {
                    return prjBuildAction.prjBuildActionCompile;
                } else if (strVal.Equals("Content", StringComparison.OrdinalIgnoreCase)) {
                    return prjBuildAction.prjBuildActionContent;
                } else if (strVal.Equals("None", StringComparison.OrdinalIgnoreCase)) {
                    return prjBuildAction.prjBuildActionNone;
                }
            }
            return base.ConvertFrom(context, culture, value);
        }

        public override StandardValuesCollection GetStandardValues(ITypeDescriptorContext context)
        {
            return new StandardValuesCollection(new[] { prjBuildAction.prjBuildActionNone, prjBuildAction.prjBuildActionCompile, prjBuildAction.prjBuildActionContent });
        }
    }
   
    [ComVisible(true)]
    public class ProjectNodeProperties : NodeProperties, EnvDTE80.IInternalExtenderProvider
    {
        #region properties
        [SRCategoryAttribute(SR.Misc)]
        [LocDisplayName(SR.ProjectFolder)]
        [SRDescriptionAttribute(SR.ProjectFolderDescription)]
        [AutomationBrowsable(false)]
        public string ProjectFolder
        {
            get
            {
                return this.Node.ProjectMgr.ProjectFolder;
            }
        }

        [SRCategoryAttribute(SR.Misc)]
        [LocDisplayName(SR.ProjectFile)]
        [SRDescriptionAttribute(SR.ProjectFileDescription)]
        [AutomationBrowsable(false)]
        public string ProjectFile
        {
            get
            {
                return this.Node.ProjectMgr.ProjectFile;
            }
            set
            {
                this.Node.ProjectMgr.ProjectFile = value;
            }
        }

        #region non-browsable properties - used for automation only
        [Browsable(false)]
        public string Guid
        {
            get
            {
                return this.Node.ProjectMgr.ProjectIDGuid.ToString();
            }
        }

        [Browsable(false)]
        public string FileName
        {
            get
            {
                return this.Node.ProjectMgr.ProjectFile;
            }
            set
            {
                this.Node.ProjectMgr.ProjectFile = value;
            }
        }


        [Browsable(false)]
        public string FullPath
        {
            get
            {
                return CommonUtils.NormalizeDirectoryPath(this.Node.ProjectMgr.ProjectFolder);
            }
        }
        #endregion

        #endregion

        #region ctors
        public ProjectNodeProperties(ProjectNode node)
            : base(node)
        {
        }

        [Browsable(false)]
        public new ProjectNode Node {
            get {
                return (ProjectNode)base.Node;
            }
        }

        #endregion

        #region overridden methods

        /// <summary>
        /// ICustomTypeDescriptor.GetEditor
        /// To enable the "Property Pages" button on the properties browser
        /// the browse object (project properties) need to be unmanaged
        /// or it needs to provide an editor of type ComponentEditor.
        /// </summary>
        /// <param name="editorBaseType">Type of the editor</param>
        /// <returns>Editor</returns>
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope",
            Justification = "The service provider is used by the PropertiesEditorLauncher")]
        public override object GetEditor(Type editorBaseType)
        {
            // Override the scenario where we are asked for a ComponentEditor
            // as this is how the Properties Browser calls us
            if (editorBaseType == typeof(ComponentEditor))
            {
                IOleServiceProvider sp;
                ErrorHandler.ThrowOnFailure(Node.ProjectMgr.GetSite(out sp));
                return new PropertiesEditorLauncher(new ServiceProvider(sp));
            }

            return base.GetEditor(editorBaseType);
        }

        public override int GetCfgProvider(out IVsCfgProvider p)
        {
            if (this.Node != null && this.Node.ProjectMgr != null)
            {
                return this.Node.ProjectMgr.GetCfgProvider(out p);
            }

            return base.GetCfgProvider(out p);
        }

        public override string GetClassName()
        {
            return SR.GetString(SR.ProjectProperties, CultureInfo.CurrentUICulture);
        }

        #endregion

        #region IInternalExtenderProvider Members

        bool EnvDTE80.IInternalExtenderProvider.CanExtend(string extenderCATID, string extenderName, object extendeeObject)
        {
            EnvDTE80.IInternalExtenderProvider outerHierarchy = Node.GetOuterInterface<EnvDTE80.IInternalExtenderProvider>();

            if (outerHierarchy != null) {
                return outerHierarchy.CanExtend(extenderCATID, extenderName, extendeeObject);
            }
            return false;
        }

        object EnvDTE80.IInternalExtenderProvider.GetExtender(string extenderCATID, string extenderName, object extendeeObject, EnvDTE.IExtenderSite extenderSite, int cookie)
        {
            EnvDTE80.IInternalExtenderProvider outerHierarchy = Node.GetOuterInterface<EnvDTE80.IInternalExtenderProvider>();

            if (outerHierarchy != null) {
                return outerHierarchy.GetExtender(extenderCATID, extenderName, extendeeObject, extenderSite, cookie);
            }

            return null;
        }

        object EnvDTE80.IInternalExtenderProvider.GetExtenderNames(string extenderCATID, object extendeeObject)
        {
            return null;
        }

        #endregion
    }

    [ComVisible(true)]
    public class FolderNodeProperties : NodeProperties
    {
        #region properties
        [SRCategoryAttribute(SR.Misc)]
        [LocDisplayName(SR.FolderName)]
        [SRDescriptionAttribute(SR.FolderNameDescription)]
        public string FolderName
        {
            get
            {
                return this.HierarchyNode.Caption;
            }
            set
            {
                UIThread.Instance.RunSync(() => {
                    this.HierarchyNode.SetEditLabel(value);
                    this.HierarchyNode.ProjectMgr.ReDrawNode(HierarchyNode, UIHierarchyElement.Caption);
                });
            }
        }

        #region properties - used for automation only
        [Browsable(false)]
        [AutomationBrowsable(true)]
        public string FileName
        {
            get
            {
                return this.HierarchyNode.Caption;
            }
            set {
                UIThread.Instance.RunSync(() => {
                    this.HierarchyNode.SetEditLabel(value);
                    this.HierarchyNode.ProjectMgr.ReDrawNode(HierarchyNode, UIHierarchyElement.Caption);
                });
            }
        }

        [Browsable(false)]
        [AutomationBrowsable(true)]
        public string FullPath
        {
            get
            {
                return CommonUtils.NormalizeDirectoryPath(this.HierarchyNode.GetMkDocument());
            }
        }
        #endregion

        #endregion

        #region ctors
        public FolderNodeProperties(HierarchyNode node)
            : base(node)
        {
        }
        #endregion

        public override string GetClassName()
        {
            return SR.GetString(SR.FolderProperties, CultureInfo.CurrentUICulture);
        }
    }

    [CLSCompliant(false), ComVisible(true)]
    public class ReferenceNodeProperties : NodeProperties
    {
        #region properties
        [SRCategoryAttribute(SR.Misc)]
        [LocDisplayName(SR.RefName)]
        [SRDescriptionAttribute(SR.RefNameDescription)]
        [Browsable(true)]
        [AutomationBrowsable(true)]
        public override string Name
        {
            get
            {
                return this.HierarchyNode.Caption;
            }
        }

        [SRCategoryAttribute(SR.Misc)]
        [LocDisplayName(SR.CopyToLocal)]
        [SRDescriptionAttribute(SR.CopyToLocalDescription)]
        [DefaultValue(false)]
        [Browsable(false)]
        public bool CopyToLocal
        {
            get
            {
                string copyLocal = this.GetProperty(ProjectFileConstants.Private, "False");
                if (copyLocal == null || copyLocal.Length == 0)
                    return true;
                return bool.Parse(copyLocal);
            }
            set
            {
                this.SetProperty(ProjectFileConstants.Private, value.ToString());
            }
        }

        [SRCategoryAttribute(SR.Misc)]
        [LocDisplayName(SR.FullPath)]
        [SRDescriptionAttribute(SR.FullPathDescription)]
        public virtual string FullPath
        {
            get
            {
                return this.HierarchyNode.Url;
            }
        }
        #endregion

        #region fields

        ReferenceNode node;

        #endregion

        #region ctors
        public ReferenceNodeProperties(ReferenceNode node)
            : base(node)
        {
            this.node = node;
        }
        #endregion

        #region overridden methods
        public override string GetClassName()
        {
            return SR.GetString(SR.ReferenceProperties, CultureInfo.CurrentUICulture);
        }
        #endregion
    }

    [ComVisible(true)]
    public class ProjectReferencesProperties : ReferenceNodeProperties
    {
        #region fields

        ProjectReferenceNode node;

        #endregion

        #region ctors
        public ProjectReferencesProperties(ProjectReferenceNode node)
            : base(node)
        {
            this.node = node;
        }
        #endregion

        #region overriden methods
        public override string FullPath
        {
            get
            {
                return ((ProjectReferenceNode)Node).ReferencedProjectOutputPath;
            }
        }
        #endregion
    }
}