using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.IO;
using UnityEngine;
using ChillyRoom.UnityEditor.iOS.Xcode.PBX;

namespace ChillyRoom.UnityEditor.iOS.Xcode
{
    using PBXBuildFileSection           = KnownSectionBase<PBXBuildFileData>;
    using PBXFileReferenceSection       = KnownSectionBase<PBXFileReferenceData>;
    using PBXGroupSection               = KnownSectionBase<PBXGroupData>;
    using PBXContainerItemProxySection  = KnownSectionBase<PBXContainerItemProxyData>;
    using PBXReferenceProxySection      = KnownSectionBase<PBXReferenceProxyData>;
    using PBXSourcesBuildPhaseSection   = KnownSectionBase<PBXSourcesBuildPhaseData>;
    using PBXFrameworksBuildPhaseSection= KnownSectionBase<PBXFrameworksBuildPhaseData>;
    using PBXResourcesBuildPhaseSection = KnownSectionBase<PBXResourcesBuildPhaseData>;
    using PBXCopyFilesBuildPhaseSection = KnownSectionBase<PBXCopyFilesBuildPhaseData>;
    using PBXShellScriptBuildPhaseSection = KnownSectionBase<PBXShellScriptBuildPhaseData>;
    using PBXVariantGroupSection        = KnownSectionBase<PBXVariantGroupData>;
    using PBXNativeTargetSection        = KnownSectionBase<PBXNativeTargetData>;
    using PBXTargetDependencySection    = KnownSectionBase<PBXTargetDependencyData>;
    using XCBuildConfigurationSection   = KnownSectionBase<XCBuildConfigurationData>;
    using XCConfigurationListSection    = KnownSectionBase<XCConfigurationListData>;
    using UnknownSection                = KnownSectionBase<PBXObjectData>;

    // Determines the tree the given path is relative to
    public enum PBXSourceTree
    {
        Absolute,   // The path is absolute
        Source,     // The path is relative to the source folder
        Group,      // The path is relative to the folder it's in. This enum is used only internally,
        // do not use it as function parameter
        Build,      // The path is relative to the build products folder
        Developer,  // The path is relative to the developer folder
        Sdk         // The path is relative to the sdk folder
    }

    public class PBXProject
    {
        PBXProjectData m_Data = new PBXProjectData();

        // convenience accessors for public members of data. This is temporary; will be fixed by an interface change
        // of PBXProjectData
        internal PBXContainerItemProxySection containerItems { get { return m_Data.containerItems; } }
        internal PBXReferenceProxySection references         { get { return m_Data.references; } }
        internal PBXSourcesBuildPhaseSection sources         { get { return m_Data.sources; } }
        internal PBXFrameworksBuildPhaseSection frameworks   { get { return m_Data.frameworks; } }
        internal PBXResourcesBuildPhaseSection resources     { get { return m_Data.resources; } }
        internal PBXCopyFilesBuildPhaseSection copyFiles     { get { return m_Data.copyFiles; } }
        internal PBXShellScriptBuildPhaseSection shellScripts { get { return m_Data.shellScripts; } }
        internal PBXNativeTargetSection nativeTargets        { get { return m_Data.nativeTargets; } }
        internal PBXTargetDependencySection targetDependencies { get { return m_Data.targetDependencies; } }
        internal PBXVariantGroupSection variantGroups        { get { return m_Data.variantGroups; } }
        internal XCBuildConfigurationSection buildConfigs    { get { return m_Data.buildConfigs; } }
        internal XCConfigurationListSection buildConfigLists { get { return m_Data.buildConfigLists; } }
        internal PBXProjectSection project                   { get { return m_Data.project; } }

        internal PBXBuildFileData BuildFilesGet(string guid) { return m_Data.BuildFilesGet(guid); }
        internal void BuildFilesAdd(string targetGuid, PBXBuildFileData buildFile) { m_Data.BuildFilesAdd(targetGuid, buildFile); }
        internal void BuildFilesRemove(string targetGuid, string fileGuid) { m_Data.BuildFilesRemove(targetGuid, fileGuid); }
        internal PBXBuildFileData BuildFilesGetForSourceFile(string targetGuid, string fileGuid) { return m_Data.BuildFilesGetForSourceFile(targetGuid, fileGuid); }
        internal IEnumerable<PBXBuildFileData> BuildFilesGetAll() { return m_Data.BuildFilesGetAll(); }
        internal void FileRefsAdd(string realPath, string projectPath, PBXGroupData parent, PBXFileReferenceData fileRef) { m_Data.FileRefsAdd(realPath, projectPath, parent, fileRef); }
        internal PBXFileReferenceData FileRefsGet(string guid) { return m_Data.FileRefsGet(guid); }
        internal PBXFileReferenceData FileRefsGetByRealPath(string path, PBXSourceTree sourceTree) { return m_Data.FileRefsGetByRealPath(path, sourceTree); }
        internal PBXFileReferenceData FileRefsGetByProjectPath(string path) { return m_Data.FileRefsGetByProjectPath(path); }
        internal void FileRefsRemove(string guid) { m_Data.FileRefsRemove(guid); }
        internal PBXGroupData GroupsGet(string guid) { return m_Data.GroupsGet(guid); }
        internal PBXGroupData GroupsGetByChild(string childGuid) { return m_Data.GroupsGetByChild(childGuid); }
        internal PBXGroupData GroupsGetMainGroup() { return m_Data.GroupsGetMainGroup(); }
        internal PBXGroupData GroupsGetByProjectPath(string sourceGroup) { return m_Data.GroupsGetByProjectPath(sourceGroup); }
        internal void GroupsAdd(string projectPath, PBXGroupData parent, PBXGroupData gr) { m_Data.GroupsAdd(projectPath, parent, gr); }
        internal void GroupsAddDuplicate(PBXGroupData gr) { m_Data.GroupsAddDuplicate(gr); }
        internal void GroupsRemove(string guid) { m_Data.GroupsRemove(guid); }
        PBXGroupData GroupsGetByName (string name) { return m_Data.GroupsGetByName (name); }
        internal FileGUIDListBase BuildSectionAny(PBXNativeTargetData target, string path, bool isFolderRef) { return m_Data.BuildSectionAny(target, path, isFolderRef); }
        internal FileGUIDListBase BuildSectionAny(string sectionGuid) { return m_Data.BuildSectionAny(sectionGuid); }

        /// <summary>
        /// Returns the path to PBX project in the given Unity build path. This function can only 
        /// be used in Unity-generated projects
        /// </summary>
        /// <param name="buildPath">The project build path</param>
        /// <returns>The path to the PBX project file that can later be opened via ReadFromFile function</returns> 
        public static string GetPBXProjectPath(string buildPath)
        {
            return PBXPath.Combine(buildPath, "Unity-iPhone.xcodeproj/project.pbxproj");
        }

        /// <summary>
        /// Returns the default main target name in Unity project.
        /// The returned target name can then be used to retrieve the GUID of the target via TargetGuidByName 
        /// function. This function can only be used in Unity-generated projects.
        /// </summary>
        /// <returns>The default main target name.</returns>
        public static string GetUnityTargetName()
        {
            return "Unity-iPhone";
        }

        /// <summary>
        /// Returns the default test target name in Unity project.
        /// The returned target name can then be used to retrieve the GUID of the target via TargetGuidByName 
        /// function. This function can only be used in Unity-generated projects.
        /// </summary>
        /// <returns>The default test target name.</returns>
        public static string GetUnityTestTargetName()
        {
            return "Unity-iPhone Tests";
        }

        /// <summary>
        /// Returns the GUID of the project. The project GUID identifies a project-wide native target which
        /// is used to set project-wide properties. This GUID can be passed to any functions that accepts 
        /// target GUIDs as parameters.
        /// </summary>
        /// <returns>The GUID of the project.</returns>
        public string ProjectGuid()
        {
            return project.project.guid;
        }

        /// <summary>
        /// Returns the GUID of the native target with the given name.
        /// In projects produced by Unity the main target can be retrieved via GetUnityTargetName function, 
        /// whereas the test target name can be retrieved by GetUnityTestTargetName function.
        /// </summary>
        /// <returns>The name of the native target.</returns>
        /// <param name="name">The GUID identifying the native target.</param>
        public string TargetGuidByName(string name)
        {
            foreach (var entry in nativeTargets.GetEntries())
                if (entry.Value.name == name)
                    return entry.Key;
            return null;
        }

        /// <summary>
        /// Checks if files with the given extension are known to PBXProject.
        /// </summary>
        /// <returns>Returns <c>true</c> if is the extension is known, <c>false</c> otherwise.</returns>
        /// <param name="ext">The file extension (leading dot is not necessary, but accepted).</param>
        public static bool IsKnownExtension(string ext)
        {
            return FileTypeUtils.IsKnownExtension(ext);
        }

        /// <summary>
        /// Checks if files with the given extension are known to PBXProject.
        /// Returns <c>true</c> if the extension is not known by PBXProject.
        /// </summary>
        /// <returns>Returns <c>true</c> if is the extension is known, <c>false</c> otherwise.</returns>
        /// <param name="ext">The file extension (leading dot is not necessary, but accepted).</param>
        public static bool IsBuildable(string ext)
        {
            return FileTypeUtils.IsBuildableFile(ext);
        }

        // The same file can be referred to by more than one project path.
        private string AddFileImpl(string path, string projectPath, PBXSourceTree tree, bool isFolderReference)
        {
            path = PBXPath.FixSlashes(path);
            projectPath = PBXPath.FixSlashes(projectPath);

            if (!isFolderReference && Path.GetExtension(path) != Path.GetExtension(projectPath))
                throw new Exception("Project and real path extensions do not match");

            string guid = FindFileGuidByProjectPath(projectPath);
            if (guid == null)
                guid = FindFileGuidByRealPath(path);
            if (guid == null)
            {
                PBXFileReferenceData fileRef;
                if (isFolderReference)
                    fileRef = PBXFileReferenceData.CreateFromFolderReference(path, PBXPath.GetFilename(projectPath), tree);
                else
                    fileRef = PBXFileReferenceData.CreateFromFile(path, PBXPath.GetFilename(projectPath), tree);
                PBXGroupData parent = CreateSourceGroup(PBXPath.GetDirectory(projectPath));
                parent.children.AddGUID(fileRef.guid);
                FileRefsAdd(path, projectPath, parent, fileRef);
                guid = fileRef.guid;
            }
            return guid;
        }

        /// <summary>
        /// Adds a new file reference to the list of known files.
        /// The group structure is automatically created to correspond to the project path.
        /// To add the file to the list of files to build, pass the returned value to [[AddFileToBuild]].
        /// </summary>
        /// <returns>The GUID of the added file. It can later be used to add the file for building to targets, etc.</returns>
        /// <param name="path">The physical path to the file on the filesystem.</param>
        /// <param name="projectPath">The project path to the file.</param>
        /// <param name="sourceTree">The source tree the path is relative to. By default it's [[PBXSourceTree.Source]].
        /// The [[PBXSourceTree.Group]] tree is not supported.</param>
        public string AddFile(string path, string projectPath, PBXSourceTree sourceTree = PBXSourceTree.Source)
        {
            if (sourceTree == PBXSourceTree.Group)
                throw new Exception("sourceTree must not be PBXSourceTree.Group");
            return AddFileImpl(path, projectPath, sourceTree, false);
        }

        /// <summary>
        /// Adds a new folder reference to the list of known files.
        /// The group structure is automatically created to correspond to the project path.
        /// To add the folder reference to the list of files to build, pass the returned value to [[AddFileToBuild]].
        /// </summary>
        /// <returns>The GUID of the added folder reference. It can later be used to add the file for building to targets, etc.</returns>
        /// <param name="path">The physical path to the folder on the filesystem.</param>
        /// <param name="projectPath">The project path to the folder.</param>
        /// <param name="sourceTree">The source tree the path is relative to. By default it's [[PBXSourceTree.Source]].
        /// The [[PBXSourceTree.Group]] tree is not supported.</param>
        public string AddFolderReference(string path, string projectPath, PBXSourceTree sourceTree = PBXSourceTree.Source)
        {
            if (sourceTree == PBXSourceTree.Group)
                throw new Exception("sourceTree must not be PBXSourceTree.Group");
            return AddFileImpl(path, projectPath, sourceTree, true);
        }

        private void AddBuildFileImpl(string targetGuid, string fileGuid, bool weak, string compileFlags)
        {
            PBXNativeTargetData target = nativeTargets[targetGuid];
            PBXFileReferenceData fileRef = FileRefsGet(fileGuid);
            
            string ext = Path.GetExtension(fileRef.path);
 
            if (FileTypeUtils.IsBuildable(ext, fileRef.isFolderReference) &&
                BuildFilesGetForSourceFile(targetGuid, fileGuid) == null)
            {
                PBXBuildFileData buildFile = PBXBuildFileData.CreateFromFile(fileGuid, weak, compileFlags);
                BuildFilesAdd(targetGuid, buildFile);
                BuildSectionAny(target, ext, fileRef.isFolderReference).files.AddGUID(buildFile.guid);
            }
        }

        /// <summary>
        /// Configures file for building for the given native target.
        /// A projects containing multiple native targets, a single file or folder reference can be 
        /// configured to be built in all, some or none of the targets. The file or folder reference is 
        /// added to appropriate build section depending on the file extension.
        /// </summary>
        /// <param name="targetGuid">The GUID of the target as returned by [[TargetGuidByName()]].</param>
        /// <param name="fileGuid">The file guid returned by [[AddFile]] or [[AddFolderReference]].</param>
        public void AddFileToBuild(string targetGuid, string fileGuid)
        {
            AddBuildFileImpl(targetGuid, fileGuid, false, null);
        }

        /// <summary>
        /// Configures file for building for the given native target with specific compiler flags.
        /// The function is equivalent to [[AddFileToBuild()]] except that compile flags are specified.
        /// A projects containing multiple native targets, a single file or folder reference can be 
        /// configured to be built in all, some or none of the targets. The file or folder reference is 
        /// added to appropriate build section depending on the file extension.
        /// </summary>
        /// <param name="targetGuid">The GUID of the target as returned by [[TargetGuidByName()]].</param>
        /// <param name="fileGuid">The file guid returned by [[AddFile]] or [[AddFolderReference]].</param>
        /// <param name="compileFlags">Compile flags to use.</param>
        public void AddFileToBuildWithFlags(string targetGuid, string fileGuid, string compileFlags)
        {
            AddBuildFileImpl(targetGuid, fileGuid, false, compileFlags);
        }

        /// <summary>
        /// Configures file for building for the given native target on specific build section.
        /// The function is equivalent to [[AddFileToBuild()]] except that specific build section is specified.
        /// A projects containing multiple native targets, a single file or folder reference can be 
        /// configured to be built in all, some or none of the targets. The file or folder reference is 
        /// added to appropriate build section depending on the file extension.
        /// </summary>
        /// <param name="targetGuid">The GUID of the target as returned by [[TargetGuidByName()]].</param>
        /// <param name="sectionGuid">The GUID of the section to add the file to.</param>
        /// <param name="fileGuid">The file guid returned by [[AddFile]] or [[AddFolderReference]].</param>
        public void AddFileToBuildSection(string targetGuid, string sectionGuid, string fileGuid)
        {
            PBXBuildFileData buildFile = PBXBuildFileData.CreateFromFile(fileGuid, false, null);
            BuildFilesAdd(targetGuid, buildFile);
            BuildSectionAny(sectionGuid).files.AddGUID(buildFile.guid);
        }

        /// <summary>
        /// Returns compile flags set for the specific file.
        /// Null is returned if the file has no configured compile flags or the file is not configured for
        /// building on the given target.
        /// </summary>
        /// <returns>The compile flags for the specified file.</returns>
        /// <param name="targetGuid">The GUID of the target as returned by [[TargetGuidByName()]].</param>
        /// <param name="fileGuid">The GUID of the file.</param>
        public List<string> GetCompileFlagsForFile(string targetGuid, string fileGuid)
        {
            var buildFile = BuildFilesGetForSourceFile(targetGuid, fileGuid);
            if (buildFile == null)
                return null;
            if (buildFile.compileFlags == null)
                return new List<string>();
            return new List<string>(buildFile.compileFlags.Split(new char[]{' '}, StringSplitOptions.RemoveEmptyEntries));
        }

        /// <summary>
        /// Sets the compilation flags for the given file in the given target.
        /// </summary>
        /// <param name="targetGuid">The GUID of the target as returned by [[TargetGuidByName()]].</param>
        /// <param name="fileGuid">The GUID of the file.</param>
        /// <param name="compileFlags">The list of compile flags or null if the flags should be unset.</param>
        public void SetCompileFlagsForFile(string targetGuid, string fileGuid, List<string> compileFlags)
        {
            var buildFile = BuildFilesGetForSourceFile(targetGuid, fileGuid);
            if (buildFile == null)
                return;
            if (compileFlags == null)
                buildFile.compileFlags = null;
            else
                buildFile.compileFlags = string.Join(" ", compileFlags.ToArray());
        }

        /// <summary>
        /// Adds an asset tag for the given file.
        /// The asset tags identify resources that will be downloaded via On Demand Resources functionality. 
        /// A request for specific tag will initiate download of all files, configured for that tag.
        /// </summary>
        /// <param name="targetGuid">The GUID of the target as returned by [[TargetGuidByName()]].</param>
        /// <param name="fileGuid">The GUID of the file.</param>
        /// <param name="tag">The name of the asset tag.</param>
        public void AddAssetTagForFile(string targetGuid, string fileGuid, string tag)
        {
            var buildFile = BuildFilesGetForSourceFile(targetGuid, fileGuid);
            if (buildFile == null)
                return;
            if (!buildFile.assetTags.Contains(tag))
                buildFile.assetTags.Add(tag);
            if (!project.project.knownAssetTags.Contains(tag))
                project.project.knownAssetTags.Add(tag);
        }

        /// <summary>
        /// Removes an asset tag for the given file.
        /// The function does nothing if the file is not configured for building in the given target or if
        /// the asset tag is not present in the list of asset tags configured for file. If the file was the
        /// last file referring to the given tag across the Xcode project, then the tag is removed from the
        /// list of known tags.
        /// </summary>
        /// <param name="targetGuid">The GUID of the target as returned by [[TargetGuidByName()]].</param>
        /// <param name="fileGuid">The GUID of the file.</param>
        /// <param name="tag">The name of the asset tag.</param>
        public void RemoveAssetTagForFile(string targetGuid, string fileGuid, string tag)
        {
            var buildFile = BuildFilesGetForSourceFile(targetGuid, fileGuid);
            if (buildFile == null)
                return;
            buildFile.assetTags.Remove(tag);
            // remove from known tags if this was the last one
            foreach (var buildFile2 in BuildFilesGetAll())
            {
                if (buildFile2.assetTags.Contains(tag))
                    return;
            }
            project.project.knownAssetTags.Remove(tag);
        }

        /// <summary>
        /// Adds the asset tag to the list of tags to download during initial installation.
        /// The function does nothing if there are no files that use the given asset tag across the project.
        /// </summary>
        /// <param name="targetGuid">The GUID of the target as returned by [[TargetGuidByName()]].</param>
        /// <param name="tag">The name of the asset tag.</param>
        public void AddAssetTagToDefaultInstall(string targetGuid, string tag)
        {
            if (!project.project.knownAssetTags.Contains(tag))
                return;
            AddBuildProperty(targetGuid, "ON_DEMAND_RESOURCES_INITIAL_INSTALL_TAGS", tag);
        }

        /// <summary>
        /// Removes the asset tag from the list of tags to download during initial installation.
        /// The function does nothing if the tag is not already configured for downloading during 
        /// initial installation.
        /// </summary>
        /// <param name="targetGuid">The GUID of the target as returned by [[TargetGuidByName()]].</param>
        /// <param name="tag">The name of the asset tag.</param>
        public void RemoveAssetTagFromDefaultInstall(string targetGuid, string tag)
        {
            UpdateBuildProperty(targetGuid, "ON_DEMAND_RESOURCES_INITIAL_INSTALL_TAGS", null, new[]{tag});
        }

        /// <summary>
        /// Removes an asset tag.
        /// Removes the given asset tag from the list of configured asset tags for all files on all targets,
        /// the list of asset tags configured for initial installation and the list of known asset tags in 
        /// the Xcode project.
        /// </summary>
        /// <param name="tag">The name of the asset tag.</param>
        public void RemoveAssetTag(string tag)
        {
            foreach (var buildFile in BuildFilesGetAll())
                buildFile.assetTags.Remove(tag);
            foreach (var targetGuid in nativeTargets.GetGuids())
                RemoveAssetTagFromDefaultInstall(targetGuid, tag);
            project.project.knownAssetTags.Remove(tag);
        }

        /// <summary>
        /// Checks if the project contains a file with the given physical path.
        /// The search is performed across all absolute source trees.
        /// </summary>
        /// <returns>Returns <c>true</c> if the project contains the file, <c>false</c> otherwise.</returns>
        /// <param name="path">The physical path of the file.</param>
        public bool ContainsFileByRealPath(string path)
        {
            return FindFileGuidByRealPath(path) != null;
        }

        /// <summary>
        /// Checks if the project contains a file with the given physical path.
        /// </summary>
        /// <returns>Returns <c>true</c> if the project contains the file, <c>false</c> otherwise.</returns>
        /// <param name="path">The physical path of the file.</param>
        /// <param name="sourceTree">The source tree path is relative to. The [[PBXSourceTree.Group]] tree is not supported.</param>
        public bool ContainsFileByRealPath(string path, PBXSourceTree sourceTree)
        {
            if (sourceTree == PBXSourceTree.Group)
                throw new Exception("sourceTree must not be PBXSourceTree.Group");
            return FindFileGuidByRealPath(path, sourceTree) != null;
        }

        /// <summary>
        /// Checks if the project contains a file with the given project path.
        /// </summary>
        /// <returns>Returns <c>true</c> if the project contains the file, <c>false</c> otherwise.</returns>
        /// <param name="path">The project path of the file.</param>
        public bool ContainsFileByProjectPath(string path)
        {
            return FindFileGuidByProjectPath(path) != null;
        }

        /// <summary>
        /// Checks whether the given system framework is a dependency of a target.
        /// The function assumes system frameworks are located in System/Library/Frameworks folder in the SDK source tree.
        /// </summary>
        /// <returns>Returns <c>true</c> if the given framework is a dependency of the given target,
        /// <c>false</c> otherwise.</returns>
        /// <param name="targetGuid">The GUID of the target as returned by [[TargetGuidByName()]].</param>
        /// <param name="framework">The name of the framework. The extension of the filename must be ".framework".</param>
        public bool ContainsFramework(string targetGuid, string framework)
        {
            var fileGuid = FindFileGuidByRealPath("System/Library/Frameworks/" + framework, PBXSourceTree.Sdk);
            if (fileGuid == null)
                return false;
 
            var buildFile = BuildFilesGetForSourceFile(targetGuid, fileGuid);
            return (buildFile != null);
        }

        /// <summary>
        /// Adds a system framework dependency for the specified target.
        /// The function assumes system frameworks are located in System/Library/Frameworks folder in the SDK source tree. 
        /// The framework is added to Frameworks logical folder in the project.
        /// </summary>
        /// <param name="targetGuid">The GUID of the target as returned by [[TargetGuidByName()]].</param>
        /// <param name="framework">The name of the framework. The extension of the filename must be ".framework".</param>
        /// <param name="weak"><c>true</c> if the framework is optional (i.e. weakly linked) required, 
        /// <c>false</c> if the framework is required.</param>
        public void AddFrameworkToProject(string targetGuid, string framework, bool weak)
        {
            string fileGuid = AddFile("System/Library/Frameworks/" + framework, "Frameworks/" + framework, PBXSourceTree.Sdk);
            AddBuildFileImpl(targetGuid, fileGuid, weak, null);
        }

        /// <summary>
        /// Removes a system framework dependency for the specified target.
        /// The function assumes system frameworks are located in System/Library/Frameworks folder in the SDK source tree.
        /// </summary>
        /// <param name="targetGuid">The GUID of the target as returned by [[TargetGuidByName()]].</param>
        /// <param name="framework">The name of the framework. The extension of the filename must be ".framework".</param>
        public void RemoveFrameworkFromProject(string targetGuid, string framework)
        {
            var fileGuid = FindFileGuidByRealPath("System/Library/Frameworks/" + framework, PBXSourceTree.Sdk);
            if (fileGuid == null)
                return;

            BuildFilesRemove(targetGuid, fileGuid);
        }

        // Allow user to add a Capability
        public bool AddCapability(string targetGuid, PBXCapabilityType capability, string entitlementsFilePath = null, bool addOptionalFramework = false)
        {
            // If the capability requires entitlements then you have to provide the name of it or we don't add the capability.
            if (capability.requiresEntitlements && entitlementsFilePath == "")
            {
                throw new Exception("Couldn't add the Xcode Capability " + capability.id + " to the PBXProject file because this capability requires an entitlement file.");
            }
            var p = project.project;

            // If an entitlement with a different name was added for another capability
            // we don't add this capacity.
            if (p.entitlementsFile != null && entitlementsFilePath != null && p.entitlementsFile != entitlementsFilePath)
            {
                if (p.capabilities.Count > 0){
                    //throw new WarningException("Attention, it seems that you have multiple entitlements file. Only one will be added the Project : " + p.entitlementsFile);
                    throw new Exception("Attention, it seems that you have multiple entitlements file. Only one will be added the Project : " + p.entitlementsFile);
                }

                return false;
            }

            // Add the capability only if it doesn't already exist.
            if (p.capabilities.Contains(new PBXCapabilityType.TargetCapabilityPair(targetGuid, capability)))
            {
                //throw new WarningException("This capability has already been added. Method ignored");
            }

            p.capabilities.Add(new PBXCapabilityType.TargetCapabilityPair(targetGuid, capability));

            // Add the required framework.
            if (capability.framework != "" && !capability.optionalFramework ||
               (capability.framework != "" && capability.optionalFramework && addOptionalFramework))
            {
                AddFrameworkToProject(targetGuid, capability.framework, false);
            }

            // Finally add the entitlement code signing if it wasn't added before.
            if (entitlementsFilePath != null && p.entitlementsFile == null)
            {
                p.entitlementsFile = entitlementsFilePath;
                AddFileImpl(entitlementsFilePath,  entitlementsFilePath, PBXSourceTree.Source, false);
                SetBuildProperty(targetGuid, "CODE_SIGN_ENTITLEMENTS", PBXPath.FixSlashes(entitlementsFilePath));
            }
            return true;
        }

        // The Xcode project needs a team set to be able to complete code signing or to add some capabilities.
        public void SetTeamId(string targetGuid, string teamId)
        {
            SetBuildProperty(targetGuid, "DEVELOPMENT_TEAM", teamId);
            project.project.teamIDs.Add(targetGuid, teamId);
        }

        /// <summary>
        /// Finds a file with the given physical path in the project, if any.
        /// </summary>
        /// <returns>The GUID of the file if the search succeeded, null otherwise.</returns>
        /// <param name="path">The physical path of the file.</param>
        /// <param name="sourceTree">The source tree path is relative to. The [[PBXSourceTree.Group]] tree is not supported.</param>
        public string FindFileGuidByRealPath(string path, PBXSourceTree sourceTree)
        {
            if (sourceTree == PBXSourceTree.Group)
                throw new Exception("sourceTree must not be PBXSourceTree.Group");
            path = PBXPath.FixSlashes(path);
            var fileRef = FileRefsGetByRealPath(path, sourceTree);
            if (fileRef != null)
                return fileRef.guid;
            return null;
        }

        /// <summary>
        /// Finds a file with the given physical path in the project, if any.
        /// The search is performed across all absolute source trees.
        /// </summary>
        /// <returns>The GUID of the file if the search succeeded, null otherwise.</returns>
        /// <param name="path">The physical path of the file.</param>
        public string FindFileGuidByRealPath(string path)
        {
            path = PBXPath.FixSlashes(path);

            foreach (var tree in FileTypeUtils.AllAbsoluteSourceTrees())
            {
                string res = FindFileGuidByRealPath(path, tree);
                if (res != null)
                    return res;
            }
            return null;
        }

        /// <summary>
        /// Finds a file with the given project path in the project, if any.
        /// </summary>
        /// <returns>The GUID of the file if the search succeeded, null otherwise.</returns>
        /// <param name="path">The project path of the file.</param>
        public string FindFileGuidByProjectPath(string path)
        {
            path = PBXPath.FixSlashes(path);
            var fileRef = FileRefsGetByProjectPath(path);
            if (fileRef != null)
                return fileRef.guid;
            return null;
        }

        /// <summary>
        /// Removes given file from the list of files to build for the given target.
        /// </summary>
        /// <param name="targetGuid">The GUID of the target as returned by [[TargetGuidByName()]].</param>
        /// <param name="fileGuid">The GUID of the file or folder reference.</param>
        public void RemoveFileFromBuild(string targetGuid, string fileGuid)
        {
            var buildFile = BuildFilesGetForSourceFile(targetGuid, fileGuid);
            if (buildFile == null)
                return;
            BuildFilesRemove(targetGuid, fileGuid);

            string buildGuid = buildFile.guid;
            if (buildGuid != null)
            {
                foreach (var section in sources.GetEntries())
                    section.Value.files.RemoveGUID(buildGuid);
                foreach (var section in resources.GetEntries())
                    section.Value.files.RemoveGUID(buildGuid);
                foreach (var section in copyFiles.GetEntries())
                    section.Value.files.RemoveGUID(buildGuid);
                foreach (var section in frameworks.GetEntries())
                    section.Value.files.RemoveGUID(buildGuid);
            }
        }

        /// <summary>
        /// Removes the given file from project.
        /// The file is removed from the list of files to build for each native target and also removed 
        /// from the list of known files.
        /// </summary>
        /// <param name="fileGuid">The GUID of the file or folder reference.</param>
        public void RemoveFile(string fileGuid)
        {
            if (fileGuid == null)
                return;

            // remove from parent
            PBXGroupData parent = GroupsGetByChild(fileGuid);
            if (parent != null)
                parent.children.RemoveGUID(fileGuid);
            RemoveGroupIfEmpty(parent);

            // remove actual file
            foreach (var target in nativeTargets.GetEntries())
                RemoveFileFromBuild(target.Value.guid, fileGuid);
            FileRefsRemove(fileGuid);
        }

        void RemoveGroupIfEmpty(PBXGroupData gr)
        {
            if (gr.children.Count == 0 && gr != GroupsGetMainGroup())
            {
                // remove from parent
                PBXGroupData parent = GroupsGetByChild(gr.guid);
                parent.children.RemoveGUID(gr.guid);
                RemoveGroupIfEmpty(parent);

                // remove actual group
                GroupsRemove(gr.guid);
            }
        }

        private void RemoveGroupChildrenRecursive(PBXGroupData parent)
        {
            List<string> children = new List<string>(parent.children);
            parent.children.Clear();
            foreach (string guid in children)
            {
                PBXFileReferenceData file = FileRefsGet(guid);
                if (file != null)
                {
                    foreach (var target in nativeTargets.GetEntries())
                        RemoveFileFromBuild(target.Value.guid, guid);
                    FileRefsRemove(guid);
                    continue;
                }

                PBXGroupData gr = GroupsGet(guid);
                if (gr != null)
                {
                    RemoveGroupChildrenRecursive(gr);
                    GroupsRemove(gr.guid);
                    continue;
                }
            }
        }

        internal void RemoveFilesByProjectPathRecursive(string projectPath)
        {
            projectPath = PBXPath.FixSlashes(projectPath);
            PBXGroupData gr = GroupsGetByProjectPath(projectPath);
            if (gr == null)
                return;
            RemoveGroupChildrenRecursive(gr);
            RemoveGroupIfEmpty(gr);
        }

        // Returns null on error
        internal List<string> GetGroupChildrenFiles(string projectPath)
        {
            projectPath = PBXPath.FixSlashes(projectPath);
            PBXGroupData gr = GroupsGetByProjectPath(projectPath);
            if (gr == null)
                return null;
            var res = new List<string>();
            foreach (var guid in gr.children)
            {
                PBXFileReferenceData fileRef = FileRefsGet(guid);
                if (fileRef != null)
                    res.Add(fileRef.name);
            }
            return res;
        }

        // Returns an empty dictionary if no group or files are found
        internal HashSet<string> GetGroupChildrenFilesRefs(string projectPath)
        {
            projectPath = PBXPath.FixSlashes(projectPath);
            PBXGroupData gr = GroupsGetByProjectPath(projectPath);
            if (gr == null)
                return new HashSet<string>();
            HashSet<string> res = new HashSet<string>();
            foreach (var guid in gr.children)
            {
                PBXFileReferenceData fileRef = FileRefsGet(guid);
                if (fileRef != null)
                    res.Add(fileRef.path);
            }
            return res == null ? new HashSet<string> () : res;
        }

        internal HashSet<string> GetFileRefsByProjectPaths(IEnumerable<string> paths)
        {
            HashSet<string> ret = new HashSet<string>();
            foreach (string path in paths)
            {
                string fixedPath = PBXPath.FixSlashes(path);
                var fileRef = FileRefsGetByProjectPath(fixedPath);
                if (fileRef != null)
                    ret.Add(fileRef.path);
            }
            return ret;
        }

        private PBXGroupData GetPBXGroupChildByName(PBXGroupData group, string name)
        {
            foreach (string guid in group.children)
            {
                var gr = GroupsGet(guid);
                if (gr != null && gr.name == name)
                    return gr;
            }
            return null;
        }

        /// Creates source group identified by sourceGroup, if needed, and returns it.
        /// If sourceGroup is empty or null, root group is returned
        private PBXGroupData CreateSourceGroup(string sourceGroup)
        {
            sourceGroup = PBXPath.FixSlashes(sourceGroup);

            if (sourceGroup == null || sourceGroup == "")
                return GroupsGetMainGroup();

            PBXGroupData gr = GroupsGetByProjectPath(sourceGroup);
            if (gr != null)
                return gr;

            // the group does not exist -- create new
            gr = GroupsGetMainGroup();

            var elements = PBXPath.Split(sourceGroup);
            string projectPath = null;
            foreach (string pathEl in elements)
            {
                if (projectPath == null)
                    projectPath = pathEl;
                else
                    projectPath += "/" + pathEl;

                PBXGroupData child = GetPBXGroupChildByName(gr, pathEl);
                if (child != null)
                    gr = child;
                else
                {
                    PBXGroupData newGroup = PBXGroupData.Create(pathEl, pathEl, PBXSourceTree.Group);
                    gr.children.AddGUID(newGroup.guid);
                    GroupsAdd(projectPath, gr, newGroup);
                    gr = newGroup;
                }
            }
            return gr;
        }

        /// <summary>
        /// Adds an external project dependency to the project.
        /// </summary>
        /// <param name="path">The path to the external Xcode project (the .xcodeproj file).</param>
        /// <param name="projectPath">The project path to the new project.</param>
        /// <param name="sourceTree">The source tree the path is relative to. The [[PBXSourceTree.Group]] tree is not supported.</param>
        internal void AddExternalProjectDependency(string path, string projectPath, PBXSourceTree sourceTree)
        {
            if (sourceTree == PBXSourceTree.Group)
                throw new Exception("sourceTree must not be PBXSourceTree.Group");
            path = PBXPath.FixSlashes(path);
            projectPath = PBXPath.FixSlashes(projectPath);

            // note: we are duplicating products group for the project reference. Otherwise Xcode crashes.
            PBXGroupData productGroup = PBXGroupData.CreateRelative("Products");
            GroupsAddDuplicate(productGroup); // don't use GroupsAdd here

            PBXFileReferenceData fileRef = PBXFileReferenceData.CreateFromFile(path, Path.GetFileName(projectPath),
                                                                               sourceTree);
            FileRefsAdd(path, projectPath, null, fileRef);
            CreateSourceGroup(PBXPath.GetDirectory(projectPath)).children.AddGUID(fileRef.guid);

            project.project.AddReference(productGroup.guid, fileRef.guid);
        }

        /** This function must be called only after the project the library is in has
            been added as a dependency via AddExternalProjectDependency. projectPath must be
            the same as the 'path' parameter passed to the AddExternalProjectDependency.
            remoteFileGuid must be the guid of the referenced file as specified in
            PBXFileReference section of the external project

            TODO: what. is remoteInfo entry in PBXContainerItemProxy? Is in referenced project name or
            referenced library name without extension?
        */
        internal void AddExternalLibraryDependency(string targetGuid, string filename, string remoteFileGuid, string projectPath,
                                                 string remoteInfo)
        {
            PBXNativeTargetData target = nativeTargets[targetGuid];
            filename = PBXPath.FixSlashes(filename);
            projectPath = PBXPath.FixSlashes(projectPath);

            // find the products group to put the new library in
            string projectGuid = FindFileGuidByRealPath(projectPath);
            if (projectGuid == null)
                throw new Exception("No such project");

            string productsGroupGuid = null;
            foreach (var proj in project.project.projectReferences)
            {
                if (proj.projectRef == projectGuid)
                {
                    productsGroupGuid = proj.group;
                    break;
                }
            }

            if (productsGroupGuid == null)
                throw new Exception("Malformed project: no project in project references");

            PBXGroupData productGroup = GroupsGet(productsGroupGuid);

            // verify file extension
            string ext = Path.GetExtension(filename);
            if (!FileTypeUtils.IsBuildableFile(ext))
                throw new Exception("Wrong file extension");

            // create ContainerItemProxy object
            var container = PBXContainerItemProxyData.Create(projectGuid, "2", remoteFileGuid, remoteInfo);
            containerItems.AddEntry(container);

            // create a reference and build file for the library
            string typeName = FileTypeUtils.GetTypeName(ext);

            var libRef = PBXReferenceProxyData.Create(filename, typeName, container.guid, "BUILT_PRODUCTS_DIR");
            references.AddEntry(libRef);
            PBXBuildFileData libBuildFile = PBXBuildFileData.CreateFromFile(libRef.guid, false, null);
            BuildFilesAdd(targetGuid, libBuildFile);
            BuildSectionAny(target, ext, false).files.AddGUID(libBuildFile.guid);

            // add to products folder
            productGroup.children.AddGUID(libRef.guid);
        }

        /// <summary>
        /// Creates a new native target.
        /// Target-specific build configurations are automatically created for each known build configuration name.
        /// Note, that this is a requirement that follows from the structure of Xcode projects, not an implementation
        /// detail of this function. The function creates a product file reference in the "Products" project folder 
        /// which refers to the target artifact that is built via this target.
        /// </summary>
        /// <returns>The GUID of the new target.</returns>
        /// <param name="name">The name of the new target.</param>
        /// <param name="ext">The file extension of the target artifact (leading dot is not necessary, but accepted).</param>
        /// <param name="type">The type of the target. For example:
        /// "com.apple.product-type.app-extension" - App extension,
        /// "com.apple.product-type.application.watchapp2" - WatchKit 2 application</param>
        public string AddTarget(string name, string ext, string type)
        {
            var buildConfigList = XCConfigurationListData.Create();
            buildConfigLists.AddEntry(buildConfigList);
            
            // create build file reference
            string fullName = name + "." + FileTypeUtils.TrimExtension(ext);
            var productFileRef = AddFile(fullName, "Products/" + fullName, PBXSourceTree.Build);
            var newTarget = PBXNativeTargetData.Create(name, productFileRef, type, buildConfigList.guid);
            nativeTargets.AddEntry(newTarget);
            project.project.targets.Add(newTarget.guid);

            foreach (var buildConfigName in BuildConfigNames())
                AddBuildConfigForTarget(newTarget.guid, buildConfigName);
            
            return newTarget.guid;
        }

        private IEnumerable<string> GetAllTargetGuids()
        {
            var targets = new List<string>();

            targets.Add(project.project.guid);
            targets.AddRange(nativeTargets.GetGuids());

            return targets;
        }

        /// <summary>
        /// Returns the file reference of the artifact created by building target.
        /// </summary>
        /// <returns>The file reference of the artifact created by building target.</returns>
        /// <param name="targetGuid">The GUID of the target as returned by [[TargetGuidByName()]].</param>
        public string GetTargetProductFileRef(string targetGuid)
        {
            return nativeTargets[targetGuid].productReference;
        }

        /// <summary>
        /// Sets up a dependency between two targets.
        /// </summary>
        /// <param name="targetGuid">The GUID of the target that is depending on the dependency.</param>
        /// <param name="targetDependencyGuid">The GUID of the dependency target</param>
        internal void AddTargetDependency(string targetGuid, string targetDependencyGuid)
        {
            string dependencyName = nativeTargets[targetDependencyGuid].name;
            var containerProxy = PBXContainerItemProxyData.Create(project.project.guid, "1", targetDependencyGuid, dependencyName);
            containerItems.AddEntry(containerProxy);

            var targetDependency = PBXTargetDependencyData.Create(targetDependencyGuid, containerProxy.guid);
            targetDependencies.AddEntry(targetDependency);

            nativeTargets[targetGuid].dependencies.AddGUID(targetDependency.guid);
        }

        // Returns the GUID of the new configuration
        // targetGuid can be either native target or the project target.
        private string AddBuildConfigForTarget(string targetGuid, string name)
        {
            if (BuildConfigByName(targetGuid, name) != null)
            {
                throw new Exception(String.Format("A build configuration by name {0} already exists for target {1}",
                                                  targetGuid, name));
            }
            var buildConfig = XCBuildConfigurationData.Create(name);
            buildConfigs.AddEntry(buildConfig);

            buildConfigLists[GetConfigListForTarget(targetGuid)].buildConfigs.AddGUID(buildConfig.guid);
            return buildConfig.guid;
        }

        private void RemoveBuildConfigForTarget(string targetGuid, string name)
        {
            var buildConfigGuid = BuildConfigByName(targetGuid, name);
            if (buildConfigGuid == null)
                return;
            buildConfigs.RemoveEntry(buildConfigGuid);
            buildConfigLists[GetConfigListForTarget(targetGuid)].buildConfigs.RemoveGUID(buildConfigGuid);
        }

        /// <summary>
        /// Returns the GUID of build configuration with the given name for the specific target.
        /// Null is returned if such configuration does not exist.
        /// </summary>
        /// <returns>The GUID of the build configuration or null if it does not exist.</returns>
        /// <param name="targetGuid">The GUID of the target as returned by [[TargetGuidByName()]].</param>
        /// <param name="name">The name of the build configuration.</param>
        public string BuildConfigByName(string targetGuid, string name)
        {
            foreach (string guid in buildConfigLists[GetConfigListForTarget(targetGuid)].buildConfigs)
            {
                var buildConfig = buildConfigs[guid];
                if (buildConfig != null && buildConfig.name == name)
                    return buildConfig.guid;
            }
            return null;
        }

        /// <summary>
        /// Returns the names of the build configurations available in the project.
        /// The number and names of the build configurations is a project-wide setting. Each target has the
        /// same number of build configurations and the names of these build configurations is the same.
        /// In other words, [[BuildConfigByName()]] will succeed for all targets in the project and all
        /// build configuration names returned by this function.
        /// </summary>
        /// <returns>An array of build config names.</returns>
        public IEnumerable<string> BuildConfigNames()
        {
            var names = new List<string>();
            // We use the project target to fetch the build configs
            foreach (var guid in buildConfigLists[project.project.buildConfigList].buildConfigs)
                names.Add(buildConfigs[guid].name);

            return names;
        }

        /// <summary>
        /// Creates a new set of build configurations for all targets in the project.
        /// The number and names of the build configurations is a project-wide setting. Each target has the
        /// same number of build configurations and the names of these build configurations is the same.
        /// The created configurations are initially empty. Care must be taken to fill them with reasonable 
        /// defaults.
        /// The function throws an exception if a build configuration with the given name already exists.
        /// </summary>
        /// <param name="name">The name of the build configuration.</param>
        public void AddBuildConfig(string name)
        {
            foreach (var targetGuid in GetAllTargetGuids())
                AddBuildConfigForTarget(targetGuid, name);
        }

        /// <summary>
        /// Removes all build configurations with the given name from all targets in the project.
        /// The number and names of the build configurations is a project-wide setting. Each target has the
        /// same number of build configurations and the names of these build configurations is the same.
        /// The function does nothing if the build configuration with the specified name does not exist.
        /// </summary>
        /// <param name="name">The name of the build configuration.</param>
        public void RemoveBuildConfig(string name)
        {
            foreach (var targetGuid in GetAllTargetGuids())
                RemoveBuildConfigForTarget(targetGuid, name);   
        }

        /// <summary>
        /// Creates a new sources build phase for given target.
        /// The new phase is placed at the end of the list of build phases configured for the target.
        /// </summary>
        /// <returns>Returns the GUID of the new phase.</returns>
        /// <param name="targetGuid">The GUID of the target as returned by [[TargetGuidByName()]].</param>
        public string AddSourcesBuildPhase(string targetGuid)
        {
            var phase = PBXSourcesBuildPhaseData.Create();
            sources.AddEntry(phase);
            nativeTargets[targetGuid].phases.AddGUID(phase.guid);
            return phase.guid;
        }

        /// <summary>
        /// Creates a new resources build phase for given target.
        /// The new phase is placed at the end of the list of build phases configured for the target.
        /// </summary>
        /// <returns>Returns the GUID of the new phase.</returns>
        /// <param name="targetGuid">The GUID of the target as returned by [[TargetGuidByName()]].</param>
        public string AddResourcesBuildPhase(string targetGuid)
        {
            var phase = PBXResourcesBuildPhaseData.Create();
            resources.AddEntry(phase);
            nativeTargets[targetGuid].phases.AddGUID(phase.guid);
            return phase.guid;
        }

        /// <summary>
        /// Creates a new frameworks build phase for given target.
        /// The new phase is placed at the end of the list of build phases configured for the target.
        /// </summary>
        /// <returns>Returns the GUID of the new phase.</returns>
        /// <param name="targetGuid">The GUID of the target as returned by [[TargetGuidByName()]].</param>
        public string AddFrameworksBuildPhase(string targetGuid)
        {
            var phase = PBXFrameworksBuildPhaseData.Create();
            frameworks.AddEntry(phase);
            nativeTargets[targetGuid].phases.AddGUID(phase.guid);
            return phase.guid;
        }

        /// <summary>
        /// Creates a new copy files build phase for given target.
        /// The new phase is placed at the end of the list of build phases configured for the target.
        /// </summary>
        /// <returns>Returns the GUID of the new phase.</returns>
        /// <param name="targetGuid">The GUID of the target as returned by [[TargetGuidByName()]].</param>
        /// <param name="name">The name of the phase.</param>
        /// <param name="dstPath">The destination path.</param>
        /// <param name="subfolderSpec">The "subfolder spec". The following usages are known:
        /// - "13" for embedding app extension content
        /// - "16" for embedding watch content</param>
        public string AddCopyFilesBuildPhase(string targetGuid, string name, string dstPath, string subfolderSpec)
        {
            var phase = PBXCopyFilesBuildPhaseData.Create(name, dstPath, subfolderSpec);
            copyFiles.AddEntry(phase);
            nativeTargets[targetGuid].phases.AddGUID(phase.guid);
            return phase.guid;
        }
 
        internal string GetConfigListForTarget(string targetGuid)
        {
            if (targetGuid == project.project.guid)
                return project.project.buildConfigList;
            else
            return nativeTargets[targetGuid].buildConfigList;
        }

        // Sets the baseConfigurationReference key for a XCBuildConfiguration. 
        // If the argument is null, the base configuration is removed.
        internal void SetBaseReferenceForConfig(string configGuid, string baseReference)
        {
            buildConfigs[configGuid].baseConfigurationReference = baseReference;
        }

        /// <summary>
        /// Adds a value to build property list in all build configurations for the specified target.
        /// Duplicate build properties are ignored. Values for names "LIBRARY_SEARCH_PATHS" and 
        /// "FRAMEWORK_SEARCH_PATHS" are quoted if they contain spaces.
        /// </summary>
        /// <param name="targetGuid">The GUID of the target as returned by [[TargetGuidByName()]].</param>
        /// <param name="name">The name of the build property.</param>
        /// <param name="value">The value of the build property.</param>
        public void AddBuildProperty(string targetGuid, string name, string value)
        {
            foreach (string guid in buildConfigLists[GetConfigListForTarget(targetGuid)].buildConfigs)
                AddBuildPropertyForConfig(guid, name, value);
        }

        /// <summary>
        /// Adds a value to build property list in all build configurations for the specified targets.
        /// Duplicate build properties are ignored. Values for names "LIBRARY_SEARCH_PATHS" and 
        /// "FRAMEWORK_SEARCH_PATHS" are quoted if they contain spaces.
        /// </summary>
        /// <param name="targetGuids">The GUIDs of the target as returned by [[TargetGuidByName()]].</param>
        /// <param name="name">The name of the build property.</param>
        /// <param name="value">The value of the build property.</param>
        public void AddBuildProperty(IEnumerable<string> targetGuids, string name, string value)
        {
            foreach (string t in targetGuids)
                AddBuildProperty(t, name, value);
        }

        /// <summary>
        /// Adds a value to build property list of the given build configuration
        /// Duplicate build properties are ignored. Values for names "LIBRARY_SEARCH_PATHS" and 
        /// "FRAMEWORK_SEARCH_PATHS" are quoted if they contain spaces.
        /// </summary>
        /// <param name="configGuid">The GUID of the build configuration as returned by [[BuildConfigByName()]].</param>
        /// <param name="name">The name of the build property.</param>
        /// <param name="value">The value of the build property.</param>
        public void AddBuildPropertyForConfig(string configGuid, string name, string value)
        {
            buildConfigs[configGuid].AddProperty(name, value);
        }

        /// <summary>
        /// Adds a value to build property list of the given build configurations
        /// Duplicate build properties are ignored. Values for names "LIBRARY_SEARCH_PATHS" and 
        /// "FRAMEWORK_SEARCH_PATHS" are quoted if they contain spaces.
        /// </summary>
        /// <param name="configGuids">The GUIDs of the build configurations as returned by [[BuildConfigByName()]].</param>
        /// <param name="name">The name of the build property.</param>
        /// <param name="value">The value of the build property.</param>
        public void AddBuildPropertyForConfig(IEnumerable<string> configGuids, string name, string value)
        {
            foreach (string guid in configGuids)
                AddBuildPropertyForConfig(guid, name, value);
        }

        /// <summary>
        /// Adds a value to build property list in all build configurations for the specified target.
        /// Duplicate build properties are ignored. Values for names "LIBRARY_SEARCH_PATHS" and 
        /// "FRAMEWORK_SEARCH_PATHS" are quoted if they contain spaces.
        /// </summary>
        /// <param name="targetGuid">The GUID of the target as returned by [[TargetGuidByName()]].</param>
        /// <param name="name">The name of the build property.</param>
        /// <param name="value">The value of the build property.</param>
        public void SetBuildProperty(string targetGuid, string name, string value)
        {
            foreach (string guid in buildConfigLists[GetConfigListForTarget(targetGuid)].buildConfigs)
                SetBuildPropertyForConfig(guid, name, value);
        }

        /// <summary>
        /// Adds a value to build property list in all build configurations for the specified targets.
        /// Duplicate build properties are ignored. Values for names "LIBRARY_SEARCH_PATHS" and 
        /// "FRAMEWORK_SEARCH_PATHS" are quoted if they contain spaces.
        /// </summary>
        /// <param name="targetGuids">The GUIDs of the target as returned by [[TargetGuidByName()]].</param>
        /// <param name="name">The name of the build property.</param>
        /// <param name="value">The value of the build property.</param>
        public void SetBuildProperty(IEnumerable<string> targetGuids, string name, string value)
        {
            foreach (string t in targetGuids)
                SetBuildProperty(t, name, value);
        }
        public void SetBuildPropertyForConfig(string configGuid, string name, string value)
        {
            buildConfigs[configGuid].SetProperty(name, value);
        }
        public void SetBuildPropertyForConfig(IEnumerable<string> configGuids, string name, string value)
        {
            foreach (string guid in configGuids)
                SetBuildPropertyForConfig(guid, name, value);
        }

        internal void RemoveBuildProperty(string targetGuid, string name)
        {
            foreach (string guid in buildConfigLists[GetConfigListForTarget(targetGuid)].buildConfigs)
                RemoveBuildPropertyForConfig(guid, name);
        }
        internal void RemoveBuildProperty(IEnumerable<string> targetGuids, string name)
        {
            foreach (string t in targetGuids)
                RemoveBuildProperty(t, name);
        }
        internal void RemoveBuildPropertyForConfig(string configGuid, string name)
        {
            buildConfigs[configGuid].RemoveProperty(name);
        }
        internal void RemoveBuildPropertyForConfig(IEnumerable<string> configGuids, string name)
        {
            foreach (string guid in configGuids)
                RemoveBuildPropertyForConfig(guid, name);
        }

        internal void RemoveBuildPropertyValueList(string targetGuid, string name, IEnumerable<string> valueList)
        {
            foreach (string guid in buildConfigLists[GetConfigListForTarget(targetGuid)].buildConfigs)
                RemoveBuildPropertyValueListForConfig(guid, name, valueList);
        }
        internal void RemoveBuildPropertyValueList(IEnumerable<string> targetGuids, string name, IEnumerable<string> valueList)
        {
            foreach (string t in targetGuids)
                RemoveBuildPropertyValueList(t, name, valueList);
        }
        internal void RemoveBuildPropertyValueListForConfig(string configGuid, string name, IEnumerable<string> valueList)
        {
            buildConfigs[configGuid].RemovePropertyValueList(name, valueList);
        }
        internal void RemoveBuildPropertyValueListForConfig(IEnumerable<string> configGuids, string name, IEnumerable<string> valueList)
        {
            foreach (string guid in configGuids)
                RemoveBuildPropertyValueListForConfig(guid, name, valueList);
        }

        /// Interprets the value of the given property as a set of space-delimited strings, then
        /// removes strings equal to items to removeValues and adds strings in addValues.
        public void UpdateBuildProperty(string targetGuid, string name, 
                                        IEnumerable<string> addValues, IEnumerable<string> removeValues)
        {
            foreach (string guid in buildConfigLists[GetConfigListForTarget(targetGuid)].buildConfigs)
                UpdateBuildPropertyForConfig(guid, name, addValues, removeValues);
        }
        public void UpdateBuildProperty(IEnumerable<string> targetGuids, string name, 
                                        IEnumerable<string> addValues, IEnumerable<string> removeValues)
        {
            foreach (string t in targetGuids)
                UpdateBuildProperty(t, name, addValues, removeValues);
        }
        public void UpdateBuildPropertyForConfig(string configGuid, string name, 
                                                 IEnumerable<string> addValues, IEnumerable<string> removeValues)
        {
            var config = buildConfigs[configGuid];
            if (config != null)
            {
                if (removeValues != null)
                    foreach (var v in removeValues)
                        config.RemovePropertyValue(name, v);
                if (addValues != null)
                    foreach (var v in addValues)
                        config.AddProperty(name, v);
            }
        }
        public void UpdateBuildPropertyForConfig(IEnumerable<string> configGuids, string name, 
                                                 IEnumerable<string> addValues, IEnumerable<string> removeValues)
        {
            foreach (string guid in configGuids)
                UpdateBuildProperty(guid, name, addValues, removeValues);
        }

        internal string ShellScriptByName(string targetGuid, string name)
        {
            foreach (var phase in nativeTargets[targetGuid].phases)
            {
                var script = shellScripts[phase];
                if (script != null && script.name == name)
                    return script.guid;
            }
            return null;
        }

        internal void AppendShellScriptBuildPhase(string targetGuid, string name, string shellPath, string shellScript)
        {
            PBXShellScriptBuildPhaseData shellScriptPhase = PBXShellScriptBuildPhaseData.Create(name, shellPath, shellScript);

            shellScripts.AddEntry(shellScriptPhase);
            nativeTargets[targetGuid].phases.AddGUID(shellScriptPhase.guid);
        }

        internal void AppendShellScriptBuildPhase(IEnumerable<string> targetGuids, string name, string shellPath, string shellScript)
        {
            PBXShellScriptBuildPhaseData shellScriptPhase = PBXShellScriptBuildPhaseData.Create(name, shellPath, shellScript);

            shellScripts.AddEntry(shellScriptPhase);
            foreach (string guid in targetGuids)
            {
                nativeTargets[guid].phases.AddGUID(shellScriptPhase.guid);
            }
        }

        public void ReadFromFile(string path)
        {
            ReadFromString(File.ReadAllText(path));
        }

        public void ReadFromString(string src)
        {
            TextReader sr = new StringReader(src);
            ReadFromStream(sr);
        }

        public void ReadFromStream(TextReader sr)
        {
            m_Data.ReadFromStream(sr);
        }

        public void WriteToFile(string path)
        {
            File.WriteAllText(path, WriteToString());
        }

        public void WriteToStream(TextWriter sw)
        {
            sw.Write(WriteToString());
        }

        public string WriteToString()
        {
            return m_Data.WriteToString();
        }

        internal PBXProjectObjectData GetProjectInternal()
        {
            return project.project;
        }

        /// <summary>
        /// Add the reference to a locale .lproj to a VariantGroup.
        /// </summary>
        /// <param name="locale">Name of the locale to use, such as zh-Hans, ja, de. Find the codes in XCode Localizations section</param>
        /// <param name="variantGroupName"> Name of the variant group for the localizable resources, such as InfoPlist.strings</param>
        /// <param name="path">path to the InfoPlist.strings file, relative to xcode project format. Make sure your *.strings files are copied over first</param>
        public void AddLocalization (string variantGroupName, string locale, string path)
        {
            path = PBXPath.FixSlashes(path);

            PBXVariantGroupData variantGroup = VariantGroupsGetByName (variantGroupName);

            if (variantGroup == null) 
            {
                variantGroup = CreateLocalizableVariantGroup (variantGroupName);
            }
            else
            {
                // get guid of the build phase
                string buildPhaseGuid = ResourceBuildPhaseByTargetName (GetUnityTargetName ());
                
                PBXBuildFileData buildFileRef = BuildFilesGetForSourceFile(TargetGuidByName(GetUnityTargetName()), variantGroup.guid);
                if(buildFileRef == null)
                {
                    Debug.Log("adding variant to resource build");
                    // add file to build target
                    string buildFileGuid = AddFileRefToBuild (TargetGuidByName (GetUnityTargetName ()), variantGroup.guid);
                    // add BuildFileRef to resouce build phase
                    AddFileToResourceBuildPhase (buildPhaseGuid, buildFileGuid);
                }
            }

            PBXFileReferenceData fileRef = FileRefsGetByRealPath(path, PBXSourceTree.Source);
            if(fileRef == null)
            {
                Debug.Log("create new file reference: " + locale + ", path: " + path);
                fileRef = PBXFileReferenceData.CreateFromFile (path, locale, PBXSourceTree.Source);
                FileRefsAdd (path, path, variantGroup, fileRef);
            }

            if (!variantGroup.children.Contains (fileRef.guid)) {
                variantGroup.children.AddGUID (fileRef.guid);
            }
        }

        private void AddToGroup (string name, string guid)
        {
            var group = GroupsGetByName (name);
            if (group != null) {
                group.children.AddGUID (guid);
            }
        }

        private PBXVariantGroupData AddVariantGroup (string name, PBXSourceTree sourceTree)
        {
            PBXVariantGroupData variantGroup = VariantGroupsGetByName (name);
            if (variantGroup == null) {
                variantGroup = PBXVariantGroupData.Create (name, sourceTree);
                m_Data.variantGroups.AddEntry (variantGroup);
            }
            return variantGroup;
        }

        private PBXVariantGroupData VariantGroupsGetByName (string name)
        {
            foreach (var group in variantGroups.GetEntries ())
                if (group.Value.name == name)
                    return group.Value;
            return null;
        }

        /// <summary>
        /// Creates a VariantGroup for localizable resources (e. g. Localizable.strings)
        /// This VariantGroup is the container of all localizable resource files for each locale (.lproj)
        /// </summary>
        /// <param name="name">Variant group name.</param>
        /// <returns>The variant group the localizable resources.</returns>
        private PBXVariantGroupData CreateLocalizableVariantGroup (string name)
        {
            // Create PBXVariantGroup
            var variantGroup = AddVariantGroup (name, PBXSourceTree.Group);
            // add PBXVariantGroup to CustomTemplate
            AddToGroup ("CustomTemplate", variantGroup.guid);
            // add PBXVariantGroup to PBXBuildFile
            string buildFileGuid = AddFileRefToBuild (TargetGuidByName (GetUnityTargetName ()), variantGroup.guid);
            // get guid of the build phase
            string buildPhaseGuid = ResourceBuildPhaseByTargetName (GetUnityTargetName ());
            // add BuildFileRef to resouce build phase
            AddFileToResourceBuildPhase (buildPhaseGuid, buildFileGuid);
            return variantGroup;
        }

        private void AddFileToResourceBuildPhase (string buildPhaseGuid, string fileGuid)
        {
            foreach (var entry in resources.GetEntries ()) {
                if (entry.Value.guid == buildPhaseGuid) {
                    entry.Value.files.AddGUID (fileGuid);
                }
            }
        }

        /// <summary>
        /// Add a file reference to a specific target.
        /// </summary>
        /// <returns>The guid of the resource build phase.</returns>
        /// <param name="target">Target name.</param>
        /// <param name="guid">The file refernce</param>
        private string AddFileRefToBuild (string target, string guid)
        {
            PBXBuildFileData data = PBXBuildFileData.CreateFromFile (guid, false, null);
            m_Data.BuildFilesAdd (target, data);
            return data.guid;
        }

        /// <summary>
        /// Gets the guid of the resouce build phase of the specified target.
        /// </summary>
        /// <returns>The guid of the resource build phase.</returns>
        /// <param name="name">Target name.</param>
        private string ResourceBuildPhaseByTargetName (string name)
        {
            PBXNativeTargetData target = TargetByName (name);
            if (target == null)
                return null;
            // phases is a GUIDList containing the build phases
            foreach (var phase in target.phases) {
                // find the build phase in the list of resources
                foreach (var resource in resources.GetEntries ()) {
                    if (resource.Value.guid == phase)
                        return resource.Value.guid;
                }
            }
            return null;
        }

        /// <summary>
        /// Gets the native target by name.
        /// </summary>
        /// <returns>The target data.</returns>
        /// <param name="name">Name.</param>
        private PBXNativeTargetData TargetByName (string name)
        {
            foreach (var entry in nativeTargets.GetEntries ()) {
                if (entry.Value.name == name)
                    return entry.Value;
            }
            return null;
        }

        /*
         * Allows the setting of target attributes in the project section such as Provisioning Style and Team ID for each target
         *
         * The Target Attributes are structured like so:
         * attributes = {
         *      TargetAttributes = {
         *          1D6058900D05DD3D006BFB54 = {
         *              DevelopmentTeam = Z6SFPV59E3;
         *              ProvisioningStyle = Manual;
         *          };
         *          5623C57217FDCB0800090B9E = {
         *              DevelopmentTeam = Z6SFPV59E3;
         *              ProvisioningStyle = Manual;
         *              TestTargetID = 1D6058900D05DD3D006BFB54;
         *          };
         *      };
         *  };
         */
        internal void SetTargetAttributes(string key, string value)
        {
            PBXElementDict properties = project.project.GetPropertiesRaw();
            PBXElementDict attributes;
            PBXElementDict targetAttributes;
            if (properties.Contains("attributes"))
			{
                attributes = properties["attributes"] as PBXElementDict;
            }
			else
			{
                attributes = properties.CreateDict("attributes");
            }

            if (attributes.Contains("TargetAttributes"))
			{
                targetAttributes = attributes["TargetAttributes"] as PBXElementDict;
            } 
			else
			{
                targetAttributes = attributes.CreateDict("TargetAttributes");
            }

            foreach (KeyValuePair<string, PBXNativeTargetData> target in nativeTargets.GetEntries()) {
                PBXElementDict targetAttributesRaw;
                if (targetAttributes.Contains(target.Key))
                {
                    targetAttributesRaw = targetAttributes[target.Key].AsDict();
                }
				else
				{
                    targetAttributesRaw = targetAttributes.CreateDict(target.Key);
                }
                targetAttributesRaw.SetString(key, value); 
            }
            project.project.UpdateVars();

        }
    }
} // namespace UnityEditor.iOS.Xcode
