context: using System.Collections.Generic;
  using System.Text;
  using System.Text.RegularExpressions;
  using System.IO;
  using System;
  using Fabric.Internal.Editor.ThirdParty.xcodeapi.PBX;
  
  namespace Fabric.Internal.Editor.ThirdParty.xcodeapi
  {
      using PBX;
      using PBXBuildFileSection           = KnownSectionBase<PBXBuildFile>;
      using PBXFileReferenceSection       = KnownSectionBase<PBXFileReference>;
      using PBXGroupSection               = KnownSectionBase<PBXGroup>;
      using PBXContainerItemProxySection  = KnownSectionBase<PBXContainerItemProxy>;
      using PBXReferenceProxySection      = KnownSectionBase<PBXReferenceProxy>;
      using PBXSourcesBuildPhaseSection   = KnownSectionBase<PBXSourcesBuildPhase>;
      using PBXFrameworksBuildPhaseSection= KnownSectionBase<PBXFrameworksBuildPhase>;
      using PBXResourcesBuildPhaseSection = KnownSectionBase<PBXResourcesBuildPhase>;
      using PBXCopyFilesBuildPhaseSection = KnownSectionBase<PBXCopyFilesBuildPhase>;
      using PBXShellScriptBuildPhaseSection = KnownSectionBase<PBXShellScriptBuildPhase>;
      using PBXVariantGroupSection        = KnownSectionBase<PBXVariantGroup>;
      using PBXNativeTargetSection        = KnownSectionBase<PBXNativeTarget>;
      using PBXTargetDependencySection    = KnownSectionBase<PBXTargetDependency>;
      using XCBuildConfigurationSection   = KnownSectionBase<XCBuildConfiguration>;
      using XCConfigurationListSection    = KnownSectionBase<XCConfigurationList>;
      using UnknownSection                = KnownSectionBase<PBXObject>;
  
      // 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
      {
          private Dictionary<string, SectionBase> m_Section = null;
          private PBXElementDict m_RootElements = null;
          private PBXElementDict m_UnknownObjects = null;
          private string m_ObjectVersion = null;
          private List<string> m_SectionOrder = null;
          
          private Dictionary<string, UnknownSection> m_UnknownSections;
          private PBXBuildFileSection buildFiles = null;
          private PBXFileReferenceSection fileRefs = null;
          private PBXGroupSection groups = null;
          private PBXContainerItemProxySection containerItems = null;
          private PBXReferenceProxySection references = null;
          private PBXSourcesBuildPhaseSection sources = null;
          private PBXFrameworksBuildPhaseSection frameworks = null;
          private PBXResourcesBuildPhaseSection resources = null;
          private PBXCopyFilesBuildPhaseSection copyFiles = null;
          private PBXShellScriptBuildPhaseSection shellScripts = null;
          private PBXNativeTargetSection nativeTargets = null;
          private PBXTargetDependencySection targetDependencies = null;
          private PBXVariantGroupSection variantGroups = null;
          private XCBuildConfigurationSection buildConfigs = null;
          private XCConfigurationListSection configs = null;
          private PBXProjectSection project = null;
          
          // FIXME: create a separate PBXObject tree to represent these relationships
  
          // A build file can be represented only once in all *BuildPhaseSection sections, thus
          // we can simplify the cache by not caring about the file extension
          private Dictionary<string, Dictionary<string, PBXBuildFile>> m_FileGuidToBuildFileMap = null;
          private Dictionary<string, PBXFileReference> m_ProjectPathToFileRefMap = null;
          private Dictionary<string, string> m_FileRefGuidToProjectPathMap = null;
          private Dictionary<PBXSourceTree, Dictionary<string, PBXFileReference>> m_RealPathToFileRefMap = null;
          private Dictionary<string, PBXGroup> m_ProjectPathToGroupMap = null;
          private Dictionary<string, string> m_GroupGuidToProjectPathMap = null;
          private Dictionary<string, PBXGroup> m_GuidToParentGroupMap = null;
  
          // targetGuid is the guid of the target that contains the section that contains the buildFile
          void BuildFilesAdd(string targetGuid, PBXBuildFile buildFile)
          {
              if (!m_FileGuidToBuildFileMap.ContainsKey(targetGuid))
                  m_FileGuidToBuildFileMap[targetGuid] = new Dictionary<string, PBXBuildFile>();
              m_FileGuidToBuildFileMap[targetGuid][buildFile.fileRef] = buildFile;
              buildFiles.AddEntry(buildFile);
          }
  
          void BuildFilesRemove(string targetGuid, string fileGuid)
          {
              var buildFile = GetBuildFileForFileGuid(targetGuid, fileGuid);
              if (buildFile != null)
              {
                  m_FileGuidToBuildFileMap[targetGuid].Remove(buildFile.fileRef);
                  buildFiles.RemoveEntry(buildFile.guid);
              }
          }
  
          PBXBuildFile GetBuildFileForFileGuid(string targetGuid, string fileGuid)
          {
              if (!m_FileGuidToBuildFileMap.ContainsKey(targetGuid))
                  return null;
              if (!m_FileGuidToBuildFileMap[targetGuid].ContainsKey(fileGuid))
                  return null;
              return m_FileGuidToBuildFileMap[targetGuid][fileGuid];
          }
  
          void FileRefsAdd(string realPath, string projectPath, PBXGroup parent, PBXFileReference fileRef)
          {
              fileRefs.AddEntry(fileRef);
              m_ProjectPathToFileRefMap.Add(projectPath, fileRef);
              m_FileRefGuidToProjectPathMap.Add(fileRef.guid, projectPath);
              m_RealPathToFileRefMap[fileRef.tree].Add(realPath, fileRef); // FIXME
              m_GuidToParentGroupMap.Add(fileRef.guid, parent);
          }
  
          void FileRefsRemove(string guid)
          {
              PBXFileReference fileRef = fileRefs[guid];
              fileRefs.RemoveEntry(guid);
              m_ProjectPathToFileRefMap.Remove(m_FileRefGuidToProjectPathMap[guid]);
              m_FileRefGuidToProjectPathMap.Remove(guid);
              foreach (var tree in FileTypeUtils.AllAbsoluteSourceTrees())
                  m_RealPathToFileRefMap[tree].Remove(fileRef.path);
              m_GuidToParentGroupMap.Remove(guid);
          }
  
          void GroupsAdd(string projectPath, PBXGroup parent, PBXGroup gr)
          {
              m_ProjectPathToGroupMap.Add(projectPath, gr);
              m_GroupGuidToProjectPathMap.Add(gr.guid, projectPath);
              m_GuidToParentGroupMap.Add(gr.guid, parent);
              groups.AddEntry(gr);
          }
  
          void GroupsRemove(string guid)
          {
              m_ProjectPathToGroupMap.Remove(m_GroupGuidToProjectPathMap[guid]);
              m_GroupGuidToProjectPathMap.Remove(guid);
              m_GuidToParentGroupMap.Remove(guid);
              groups.RemoveEntry(guid);
          }
  
          void RefreshBuildFilesMapForBuildFileGuidList(Dictionary<string, PBXBuildFile> mapForTarget,
                                                        FileGUIDListBase list)
          {
              foreach (string guid in list.files)
              {
                  var buildFile = buildFiles[guid];
                  mapForTarget[buildFile.fileRef] = buildFile;
              }
          }
  
          void CombinePaths(string path1, PBXSourceTree tree1, string path2, PBXSourceTree tree2,
                            out string resPath, out PBXSourceTree resTree)
          {
              if (tree2 == PBXSourceTree.Group)
              {
                  resPath = Path.Combine(path1, path2);
                  resTree = tree1;
                  return;
              }
              
              resPath = path2;
              resTree = tree2;
          }
  
          void RefreshMapsForGroupChildren(string projectPath, string realPath, PBXSourceTree realPathTree, PBXGroup parent)
          {
              var children = new List<string>(parent.children);
              foreach (string guid in children)
              {
                  PBXFileReference fileRef = fileRefs[guid];
                  string pPath;
                  string rPath;
                  PBXSourceTree rTree;
   
                  if (fileRef != null)
                  {
                      pPath = Path.Combine(projectPath, fileRef.name);
                      CombinePaths(realPath, realPathTree, fileRef.path, fileRef.tree, out rPath, out rTree);
  
                      m_ProjectPathToFileRefMap.Add(pPath, fileRef);
                      m_FileRefGuidToProjectPathMap.Add(fileRef.guid, pPath);
                      m_RealPathToFileRefMap[rTree].Add(rPath, fileRef);
                      m_GuidToParentGroupMap.Add(guid, parent);
                      continue;
                  }
  
                  PBXGroup gr = groups[guid];
                  if (gr != null)
                  {
                      pPath = Path.Combine(projectPath, gr.name);
                      CombinePaths(realPath, realPathTree, gr.path, gr.tree, out rPath, out rTree);
                      
                      m_ProjectPathToGroupMap.Add(pPath, gr);
                      m_GroupGuidToProjectPathMap.Add(gr.guid, pPath);
                      m_GuidToParentGroupMap.Add(guid, parent);
                      RefreshMapsForGroupChildren(pPath, rPath, rTree, gr);
                  }
              }
          }
  
          void RefreshAuxMaps()
          {
              foreach (var targetEntry in nativeTargets.entries)
              {
                  var map = new Dictionary<string, PBXBuildFile>();
                  foreach (string phaseGuid in targetEntry.Value.phases)
                  {
                      if (frameworks.entries.ContainsKey(phaseGuid))
                          RefreshBuildFilesMapForBuildFileGuidList(map, frameworks.entries[phaseGuid]);
                      if (resources.entries.ContainsKey(phaseGuid))
                          RefreshBuildFilesMapForBuildFileGuidList(map, resources.entries[phaseGuid]);
                      if (sources.entries.ContainsKey(phaseGuid))
                          RefreshBuildFilesMapForBuildFileGuidList(map, sources.entries[phaseGuid]);
                      if (copyFiles.entries.ContainsKey(phaseGuid))
                          RefreshBuildFilesMapForBuildFileGuidList(map, copyFiles.entries[phaseGuid]);
                  }
                  m_FileGuidToBuildFileMap[targetEntry.Key] = map;
              }
              RefreshMapsForGroupChildren("", "", PBXSourceTree.Source, groups[project.project.mainGroup]);
          }
  
          void Clear()
          {
              buildFiles = new PBXBuildFileSection("PBXBuildFile");
              fileRefs = new PBXFileReferenceSection("PBXFileReference");
              groups = new PBXGroupSection("PBXGroup");
              containerItems = new PBXContainerItemProxySection("PBXContainerItemProxy");
              references = new PBXReferenceProxySection("PBXReferenceProxy");
              sources = new PBXSourcesBuildPhaseSection("PBXSourcesBuildPhase");
              frameworks = new PBXFrameworksBuildPhaseSection("PBXFrameworksBuildPhase");
              resources = new PBXResourcesBuildPhaseSection("PBXResourcesBuildPhase");
              copyFiles = new PBXCopyFilesBuildPhaseSection("PBXCopyFilesBuildPhase");
              shellScripts = new PBXShellScriptBuildPhaseSection("PBXShellScriptBuildPhase");
              nativeTargets = new PBXNativeTargetSection("PBXNativeTarget");
              targetDependencies = new PBXTargetDependencySection("PBXTargetDependency");
              variantGroups = new PBXVariantGroupSection("PBXVariantGroup");
              buildConfigs = new XCBuildConfigurationSection("XCBuildConfiguration");
              configs = new XCConfigurationListSection("XCConfigurationList");
              project = new PBXProjectSection();
              m_UnknownSections = new Dictionary<string, UnknownSection>();
              
              m_Section = new Dictionary<string, SectionBase>
              {
                  { "PBXBuildFile", buildFiles },
                  { "PBXFileReference", fileRefs },
                  { "PBXGroup", groups },
                  { "PBXContainerItemProxy", containerItems },
                  { "PBXReferenceProxy", references },
                  { "PBXSourcesBuildPhase", sources },
                  { "PBXFrameworksBuildPhase", frameworks },
                  { "PBXResourcesBuildPhase", resources },
                  { "PBXCopyFilesBuildPhase", copyFiles },
                  { "PBXShellScriptBuildPhase", shellScripts },
                  { "PBXNativeTarget", nativeTargets },
                  { "PBXTargetDependency", targetDependencies },
                  { "PBXVariantGroup", variantGroups },
                  { "XCBuildConfiguration", buildConfigs },
                  { "XCConfigurationList", configs },
  
                  { "PBXProject", project },
              };
              m_RootElements = new PBXElementDict();
              m_UnknownObjects = new PBXElementDict();
              m_ObjectVersion = null;
              m_SectionOrder = new List<string>{
                  "PBXBuildFile", "PBXContainerItemProxy", "PBXCopyFilesBuildPhase", "PBXFileReference",
                  "PBXFrameworksBuildPhase", "PBXGroup", "PBXNativeTarget", "PBXProject", "PBXReferenceProxy",
                  "PBXResourcesBuildPhase", "PBXShellScriptBuildPhase", "PBXSourcesBuildPhase", "PBXTargetDependency",
                  "PBXVariantGroup", "XCBuildConfiguration", "XCConfigurationList"
              };
              m_FileGuidToBuildFileMap = new Dictionary<string, Dictionary<string, PBXBuildFile>>();
              m_ProjectPathToFileRefMap = new Dictionary<string, PBXFileReference>();
              m_FileRefGuidToProjectPathMap = new Dictionary<string, string>();
              m_RealPathToFileRefMap = new Dictionary<PBXSourceTree, Dictionary<string, PBXFileReference>>();
              foreach (var tree in FileTypeUtils.AllAbsoluteSourceTrees())
                  m_RealPathToFileRefMap.Add(tree, new Dictionary<string, PBXFileReference>());
              m_ProjectPathToGroupMap = new Dictionary<string, PBXGroup>();
              m_GroupGuidToProjectPathMap = new Dictionary<string, string>();
              m_GuidToParentGroupMap = new Dictionary<string, PBXGroup>();
          }
  
          public static string GetPBXProjectPath(string buildPath)
          {
              return Path.Combine(buildPath, "Unity-iPhone/project.pbxproj");
          }
  
          public static string GetUnityTargetName()
          {
              return "Unity-iPhone";
          }
  
          public static string GetUnityTestTargetName()
          {
              return "Unity-iPhone Tests";
          }
  
          /// Returns a guid identifying native target with name @a name
          public string TargetGuidByName(string name)
          {
              foreach (var entry in nativeTargets.entries)
                  if (entry.Value.name == name)
                      return entry.Key;
              return null;
          }
  
          private FileGUIDListBase BuildSection(PBXNativeTarget target, string path)
          {
              string ext = Path.GetExtension(path);
              var phase = FileTypeUtils.GetFileType(ext);
              switch (phase) {
              case PBXFileType.Framework:
                  foreach (var guid in target.phases)
                      if (frameworks.entries.ContainsKey(guid))
                          return frameworks.entries[guid];
                  break;
              case PBXFileType.Resource:
                  foreach (var guid in target.phases)
                      if (resources.entries.ContainsKey(guid))
                          return resources.entries[guid];
                  break;
              case PBXFileType.Source:
                  foreach (var guid in target.phases)
                      if (sources.entries.ContainsKey(guid))
                          return sources.entries[guid];
                  break;
              case PBXFileType.CopyFile:
                  foreach (var guid in target.phases)
                      if (copyFiles.entries.ContainsKey(guid))
                          return copyFiles.entries[guid];
                  break;
              }
              return null;
          }
  
          public static bool IsKnownExtension(string ext)
          {
              return FileTypeUtils.IsKnownExtension(ext);
          }
  
          public static bool IsBuildable(string ext)
          {
              return FileTypeUtils.IsBuildable(ext);
          }
  
          // The same file can be referred to by more than one project path.
          private string AddFileImpl(string path, string projectPath, PBXSourceTree tree)
          {
              path = FixSlashesInPath(path);
              projectPath = FixSlashesInPath(projectPath);
  
              string ext = Path.GetExtension(path);
              if (ext != 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)
              {
                  PBXFileReference fileRef = PBXFileReference.CreateFromFile(path, GetFilenameFromPath(projectPath), tree);
                  PBXGroup parent = CreateSourceGroup(GetDirectoryFromPath(projectPath));
                  parent.children.AddGUID(fileRef.guid);
                  FileRefsAdd(path, projectPath, parent, fileRef);
                  guid = fileRef.guid;
              }
              return guid;
          }
  
          // The extension of the files identified by path and projectPath must be the same.
          public string AddFile(string path, string projectPath)
          {
              return AddFileImpl(path, projectPath, PBXSourceTree.Source);
          }
  
          // sourceTree must not be PBXSourceTree.Group
          public string AddFile(string path, string projectPath, PBXSourceTree sourceTree)
          {
              if (sourceTree == PBXSourceTree.Group)
                  throw new Exception("sourceTree must not be PBXSourceTree.Group");
              return AddFileImpl(path, projectPath, sourceTree);
          }
  
          private void AddBuildFileImpl(string targetGuid, string fileGuid, bool weak, string compileFlags)
          {
              PBXNativeTarget target = nativeTargets[targetGuid];
              string ext = Path.GetExtension(fileRefs[fileGuid].path);
              if (FileTypeUtils.IsBuildable(ext) &&
                  GetBuildFileForFileGuid(targetGuid, fileGuid) == null)
              {
                  PBXBuildFile buildFile = PBXBuildFile.CreateFromFile(fileGuid, weak, compileFlags);
                  BuildFilesAdd(targetGuid, buildFile);
                  BuildSection(target, ext).files.AddGUID(buildFile.guid);
              }
          }
  
          public void AddFileToBuild(string targetGuid, string fileGuid)
          {
              AddBuildFileImpl(targetGuid, fileGuid, false, null);
          }
  
          public void AddFileToBuildWithFlags(string targetGuid, string fileGuid, string compileFlags)
          {
              AddBuildFileImpl(targetGuid, fileGuid, false, compileFlags);
          }
          
          // returns null on error
          // FIXME: at the moment returns all flags as the first element of the array
          public List<string> GetCompileFlagsForFile(string targetGuid, string fileGuid)
          {
              var buildFile = GetBuildFileForFileGuid(targetGuid, fileGuid);
              if (buildFile == null)
                  return null;
              if (buildFile.compileFlags == null)
                  return new List<string>();
              return new List<string>{buildFile.compileFlags};
          }
          
          public void SetCompileFlagsForFile(string targetGuid, string fileGuid, List<string> compileFlags)
          {
              var buildFile = GetBuildFileForFileGuid(targetGuid, fileGuid);
              if (buildFile == null)
                  return;
              buildFile.compileFlags = string.Join(" ", compileFlags.ToArray());
          }
  
          public bool ContainsFileByRealPath(string path)
          {
              return FindFileGuidByRealPath(path) != null;
          }
  
          // sourceTree must not be PBXSourceTree.Group
          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;
          }
  
          public bool ContainsFileByProjectPath(string path)
          {
              return FindFileGuidByProjectPath(path) != null;
          }
  
          public bool HasFramework(string framework)
          {
              return ContainsFileByRealPath("System/Library/Frameworks/"+framework);
          }
  
          /// The framework must be specified with the '.framework' extension
          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);
          }
  
          private string GetDirectoryFromPath(string path)
          {
              int pos = path.LastIndexOf('/');
              if (pos == -1)
                  return "";
              else
                  return path.Substring(0, pos);
          }
  
          private string GetFilenameFromPath(string path)
          {
              int pos = path.LastIndexOf('/');
              if (pos == -1)
                  return path;
              else
                  return path.Substring(pos + 1);
          }
  
          // sourceTree must not be PBXSourceTree.Group
          public string FindFileGuidByRealPath(string path, PBXSourceTree sourceTree)
          {
              if (sourceTree == PBXSourceTree.Group)
                  throw new Exception("sourceTree must not be PBXSourceTree.Group");
              path = FixSlashesInPath(path);
              if (m_RealPathToFileRefMap[sourceTree].ContainsKey(path))
                  return m_RealPathToFileRefMap[sourceTree][path].guid;
              return null;
          }
  
          public string FindFileGuidByRealPath(string path)
          {
              path = FixSlashesInPath(path);
  
              foreach (var tree in FileTypeUtils.AllAbsoluteSourceTrees())
              {
                  string res = FindFileGuidByRealPath(path, tree);
                  if (res != null)
                      return res;
              }
              return null;
          }
  
          public string FindFileGuidByProjectPath(string path)
          {
              path = FixSlashesInPath(path);
              if (m_ProjectPathToFileRefMap.ContainsKey(path))
                  return m_ProjectPathToFileRefMap[path].guid;
              return null;
          }
  
          public void RemoveFileFromBuild(string targetGuid, string fileGuid)
          {
              var buildFile = GetBuildFileForFileGuid(targetGuid, fileGuid);
              if (buildFile == null)
                  return;
              BuildFilesRemove(targetGuid, fileGuid);
  
              string buildGuid = buildFile.guid;
              if (buildGuid != null)
              {
                  foreach (var section in sources.entries)
                      section.Value.files.RemoveGUID(buildGuid);
                  foreach (var section in resources.entries)
                      section.Value.files.RemoveGUID(buildGuid);
                  foreach (var section in copyFiles.entries)
                      section.Value.files.RemoveGUID(buildGuid);
                  foreach (var section in frameworks.entries)
                      section.Value.files.RemoveGUID(buildGuid);
              }
          }
  
          public void RemoveFile(string fileGuid)
          {
              if (fileGuid == null)
                  return;
  
              // remove from parent
              PBXGroup parent = m_GuidToParentGroupMap[fileGuid];
              if (parent != null)
                  parent.children.RemoveGUID(fileGuid);
              RemoveGroupIfEmpty(parent);
  
              // remove actual file
              foreach (var target in nativeTargets.entries)
                  RemoveFileFromBuild(target.Value.guid, fileGuid);
              FileRefsRemove(fileGuid);
          }
  
          void RemoveGroupIfEmpty(PBXGroup gr)
          {
              if (gr.children.Count == 0 && gr.guid != project.project.mainGroup)
              {
                  // remove from parent
                  PBXGroup parent = m_GuidToParentGroupMap[gr.guid];
                  parent.children.RemoveGUID(gr.guid);
                  RemoveGroupIfEmpty(parent);
  
                  // remove actual group
                  GroupsRemove(gr.guid);
              }
          }
  
          private void RemoveGroupChildrenRecursive(PBXGroup parent)
          {
              List<string> children = new List<string>(parent.children);
              parent.children.Clear();
              foreach (string guid in children)
              {
                  PBXFileReference file = fileRefs[guid];
                  if (file != null)
                  {
                      foreach (var target in nativeTargets.entries)
                          RemoveFileFromBuild(target.Value.guid, guid);
                      FileRefsRemove(guid);
                      continue;
                  }
  
                  PBXGroup gr = groups[guid];
                  if (gr != null)
                  {
                      RemoveGroupChildrenRecursive(gr);
                      GroupsRemove(parent.guid);
                      continue;
                  }
              }
          }
  
          internal void RemoveFilesByProjectPathRecursive(string projectPath)
          {
              PBXGroup gr = GetSourceGroup(projectPath);
              if (gr == null)
                  return;
              RemoveGroupChildrenRecursive(gr);
              RemoveGroupIfEmpty(gr);
          }
  
          private PBXGroup GetPBXGroupChildByName(PBXGroup group, string name)
          {
              foreach (string guid in group.children)
              {
                  var gr = groups[guid];
                  if (gr != null && gr.name == name)
                      return gr;
              }
              return null;
          }
  
          /// Returns the source group identified by sourceGroup. If sourceGroup is empty or null,
          /// root group is returned. If no group is found, null is returned.
          private PBXGroup GetSourceGroup(string sourceGroup)
          {
              sourceGroup = FixSlashesInPath(sourceGroup);
  
              if (sourceGroup == null || sourceGroup == "")
                  return groups[project.project.mainGroup];
  
              if (m_ProjectPathToGroupMap.ContainsKey(sourceGroup))
                  return m_ProjectPathToGroupMap[sourceGroup];
              return null;
          }
  
          /// Creates source group identified by sourceGroup, if needed, and returns it.
          /// If sourceGroup is empty or null, root group is returned
          private PBXGroup CreateSourceGroup(string sourceGroup)
          {
              sourceGroup = FixSlashesInPath(sourceGroup);
  
              if (m_ProjectPathToGroupMap.ContainsKey(sourceGroup))
                  return m_ProjectPathToGroupMap[sourceGroup];
  
              PBXGroup gr = groups[project.project.mainGroup];
  
              if (sourceGroup == null || sourceGroup == "")
                  return gr;
  
              string[] elements = sourceGroup.Trim('/').Split('/');
              string projectPath = null;
              foreach (string pathEl in elements)
              {
                  if (projectPath == null)
                      projectPath = pathEl;
                  else
                      projectPath += "/" + pathEl;
  
                  PBXGroup child = GetPBXGroupChildByName(gr, pathEl);
                  if (child != null)
                      gr = child;
                  else
                  {
                      PBXGroup newGroup = PBXGroup.Create(pathEl, pathEl, PBXSourceTree.Group);
                      gr.children.AddGUID(newGroup.guid);
                      GroupsAdd(projectPath, gr, newGroup);
                      gr = newGroup;
                  }
              }
              return gr;
          }
  
          // sourceTree must not be PBXSourceTree.Group
          public void AddExternalProjectDependency(string path, string projectPath, PBXSourceTree sourceTree)
          {
              if (sourceTree == PBXSourceTree.Group)
                  throw new Exception("sourceTree must not be PBXSourceTree.Group");
              path = FixSlashesInPath(path);
              projectPath = FixSlashesInPath(projectPath);
  
              // note: we are duplicating products group for the project reference. Otherwise Xcode crashes.
              PBXGroup productGroup = PBXGroup.CreateRelative("Products");
              groups.AddEntry(productGroup); // don't use GroupsAdd here
  
              PBXFileReference fileRef = PBXFileReference.CreateFromFile(path, Path.GetFileName(projectPath),
                                                                         sourceTree);
              FileRefsAdd(path, projectPath, null, fileRef);
              CreateSourceGroup(GetDirectoryFromPath(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: wtf. is remoteInfo entry in PBXContainerItemProxy? Is in referenced project name or
              referenced library name without extension?
          */
          public void AddExternalLibraryDependency(string targetGuid, string filename, string remoteFileGuid, string projectPath,
                                                   string remoteInfo)
          {
              PBXNativeTarget target = nativeTargets[targetGuid];
              filename = FixSlashesInPath(filename);
              projectPath = FixSlashesInPath(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");
  
              PBXGroup productGroup = groups[productsGroupGuid];
  
              // verify file extension
              string ext = Path.GetExtension(filename);
              if (!FileTypeUtils.IsBuildable(ext))
                  throw new Exception("Wrong file extension");
  
              // create ContainerItemProxy object
              var container = PBXContainerItemProxy.Create(projectGuid, "2", remoteFileGuid, remoteInfo);
              containerItems.AddEntry(container);
  
              // create a reference and build file for the library
              string typeName = FileTypeUtils.GetTypeName(ext);
  
              var libRef = PBXReferenceProxy.Create(filename, typeName, container.guid, "BUILT_PRODUCTS_DIR");
              references.AddEntry(libRef);
              PBXBuildFile libBuildFile = PBXBuildFile.CreateFromFile(libRef.guid, false, null);
              BuildFilesAdd(targetGuid, libBuildFile);
              BuildSection(target, ext).files.AddGUID(libBuildFile.guid);
  
              // add to products folder
              productGroup.children.AddGUID(libRef.guid);
          }
  
          private void SetDefaultAppExtensionReleaseBuildFlags(XCBuildConfiguration config, string infoPlistPath)
          {
              config.AddProperty("ALWAYS_SEARCH_USER_PATHS", "NO");
              config.AddProperty("CLANG_CXX_LANGUAGE_STANDARD", "gnu++0x");
              config.AddProperty("CLANG_CXX_LIBRARY", "libc++");
              config.AddProperty("CLANG_ENABLE_MODULES", "YES");
              config.AddProperty("CLANG_ENABLE_OBJC_ARC", "YES");
              config.AddProperty("CLANG_WARN_BOOL_CONVERSION", "YES");
              config.AddProperty("CLANG_WARN_CONSTANT_CONVERSION", "YES");
              config.AddProperty("CLANG_WARN_DIRECT_OBJC_ISA_USAGE", "YES_ERROR");
              config.AddProperty("CLANG_WARN_EMPTY_BODY", "YES");
              config.AddProperty("CLANG_WARN_ENUM_CONVERSION", "YES");
              config.AddProperty("CLANG_WARN_INT_CONVERSION", "YES");
              config.AddProperty("CLANG_WARN_OBJC_ROOT_CLASS", "YES_ERROR");
              config.AddProperty("CLANG_WARN_UNREACHABLE_CODE", "YES");
              config.AddProperty("CLANG_WARN__DUPLICATE_METHOD_MATCH", "YES");
              config.AddProperty("COPY_PHASE_STRIP", "YES");
              config.AddProperty("ENABLE_NS_ASSERTIONS", "NO");
              config.AddProperty("ENABLE_STRICT_OBJC_MSGSEND", "YES");
              config.AddProperty("GCC_C_LANGUAGE_STANDARD", "gnu99");
              config.AddProperty("GCC_WARN_64_TO_32_BIT_CONVERSION", "YES");
              config.AddProperty("GCC_WARN_ABOUT_RETURN_TYPE", "YES_ERROR");
              config.AddProperty("GCC_WARN_UNDECLARED_SELECTOR", "YES");
              config.AddProperty("GCC_WARN_UNINITIALIZED_AUTOS", "YES_AGGRESSIVE");
              config.AddProperty("GCC_WARN_UNUSED_FUNCTION", "YES");
              config.AddProperty("INFOPLIST_FILE", infoPlistPath);
              config.AddProperty("IPHONEOS_DEPLOYMENT_TARGET", "8.0");
              config.AddProperty("LD_RUNPATH_SEARCH_PATHS", "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks");
              config.AddProperty("MTL_ENABLE_DEBUG_INFO", "NO");
              config.AddProperty("PRODUCT_NAME", "$(TARGET_NAME)");
              config.AddProperty("SKIP_INSTALL", "YES");
              config.AddProperty("VALIDATE_PRODUCT", "YES");
          }
  
          private void SetDefaultAppExtensionDebugBuildFlags(XCBuildConfiguration config, string infoPlistPath)
          {
              config.AddProperty("ALWAYS_SEARCH_USER_PATHS", "NO");
              config.AddProperty("CLANG_CXX_LANGUAGE_STANDARD", "gnu++0x");
              config.AddProperty("CLANG_CXX_LIBRARY", "libc++");
              config.AddProperty("CLANG_ENABLE_MODULES", "YES");
              config.AddProperty("CLANG_ENABLE_OBJC_ARC", "YES");
              config.AddProperty("CLANG_WARN_BOOL_CONVERSION", "YES");
              config.AddProperty("CLANG_WARN_CONSTANT_CONVERSION", "YES");
              config.AddProperty("CLANG_WARN_DIRECT_OBJC_ISA_USAGE", "YES_ERROR");
              config.AddProperty("CLANG_WARN_EMPTY_BODY", "YES");
              config.AddProperty("CLANG_WARN_ENUM_CONVERSION", "YES");
              config.AddProperty("CLANG_WARN_INT_CONVERSION", "YES");
              config.AddProperty("CLANG_WARN_OBJC_ROOT_CLASS", "YES_ERROR");
              config.AddProperty("CLANG_WARN_UNREACHABLE_CODE", "YES");
              config.AddProperty("CLANG_WARN__DUPLICATE_METHOD_MATCH", "YES");
              config.AddProperty("COPY_PHASE_STRIP", "NO");
              config.AddProperty("ENABLE_STRICT_OBJC_MSGSEND", "YES");
              config.AddProperty("GCC_C_LANGUAGE_STANDARD", "gnu99");
              config.AddProperty("GCC_DYNAMIC_NO_PIC", "NO");
              config.AddProperty("GCC_OPTIMIZATION_LEVEL", "0");
              config.AddProperty("GCC_PREPROCESSOR_DEFINITIONS", "DEBUG=1");
              config.AddProperty("GCC_PREPROCESSOR_DEFINITIONS", "$(inherited)");
              config.AddProperty("GCC_SYMBOLS_PRIVATE_EXTERN", "NO");
              config.AddProperty("GCC_WARN_64_TO_32_BIT_CONVERSION", "YES");
              config.AddProperty("GCC_WARN_ABOUT_RETURN_TYPE", "YES_ERROR");
              config.AddProperty("GCC_WARN_UNDECLARED_SELECTOR", "YES");
              config.AddProperty("GCC_WARN_UNINITIALIZED_AUTOS", "YES_AGGRESSIVE");
              config.AddProperty("GCC_WARN_UNUSED_FUNCTION", "YES");
              config.AddProperty("INFOPLIST_FILE", infoPlistPath);
              config.AddProperty("IPHONEOS_DEPLOYMENT_TARGET", "8.0");
              config.AddProperty("LD_RUNPATH_SEARCH_PATHS", "$(inherited)");
              config.AddProperty("LD_RUNPATH_SEARCH_PATHS", "@executable_path/Frameworks");
              config.AddProperty("LD_RUNPATH_SEARCH_PATHS", "@executable_path/../../Frameworks");
              config.AddProperty("MTL_ENABLE_DEBUG_INFO", "YES");
              config.AddProperty("ONLY_ACTIVE_ARCH", "YES");
              config.AddProperty("PRODUCT_NAME", "$(TARGET_NAME)");
              config.AddProperty("SKIP_INSTALL", "YES");
          }
  
          // Returns the guid of the new target
          internal string AddAppExtension(string mainTarget, string name, string infoPlistPath)
          {
              string ext = ".appex";
              string fullName = name + ext;
              var productFileRef = PBXFileReference.CreateFromFile("Products/" + fullName, "Products/" + fullName,
                                                                   PBXSourceTree.Group);
              var releaseBuildConfig = XCBuildConfiguration.Create("Release");
              buildConfigs.AddEntry(releaseBuildConfig);
              SetDefaultAppExtensionReleaseBuildFlags(releaseBuildConfig, infoPlistPath);
  
              var debugBuildConfig = XCBuildConfiguration.Create("Debug");
              buildConfigs.AddEntry(debugBuildConfig);
              SetDefaultAppExtensionDebugBuildFlags(debugBuildConfig, infoPlistPath);
  
              var buildConfigList = XCConfigurationList.Create();
              configs.AddEntry(buildConfigList);
              buildConfigList.buildConfigs.AddGUID(releaseBuildConfig.guid);
              buildConfigList.buildConfigs.AddGUID(debugBuildConfig.guid);
  
  
              var newTarget = PBXNativeTarget.Create(name, productFileRef.guid, "com.apple.product-type.app-extension", buildConfigList.guid);
              nativeTargets.AddEntry(newTarget);
              project.project.targets.Add(newTarget.guid);
  
              var sourcesBuildPhase = PBXSourcesBuildPhase.Create();
              sources.AddEntry(sourcesBuildPhase);
              newTarget.phases.AddGUID(sourcesBuildPhase.guid);
  
              var resourcesBuildPhase = PBXResourcesBuildPhase.Create();
              resources.AddEntry(resourcesBuildPhase);
              newTarget.phases.AddGUID(resourcesBuildPhase.guid);
  
              var frameworksBuildPhase = PBXFrameworksBuildPhase.Create();
              frameworks.AddEntry(frameworksBuildPhase);
              newTarget.phases.AddGUID(frameworksBuildPhase.guid);
  
              var copyFilesBuildPhase = PBXCopyFilesBuildPhase.Create("Embed App Extensions", "13");
              copyFiles.AddEntry(copyFilesBuildPhase);
              nativeTargets[mainTarget].phases.AddGUID(copyFilesBuildPhase.guid);
  
              var containerProxy = PBXContainerItemProxy.Create(project.project.guid, "1", newTarget.guid, name);
              containerItems.AddEntry(containerProxy);
  
              var targetDependency = PBXTargetDependency.Create(newTarget.guid, containerProxy.guid);
              targetDependencies.AddEntry(targetDependency);
  
              nativeTargets[mainTarget].dependencies.AddGUID(targetDependency.guid);
  
              AddFile(fullName, "Products/" + fullName, PBXSourceTree.Build);
              var buildAppCopy = PBXBuildFile.CreateFromFile(FindFileGuidByProjectPath("Products/" + fullName), false, "");
              BuildFilesAdd(mainTarget, buildAppCopy);
              copyFilesBuildPhase.files.AddGUID(buildAppCopy.guid);
  
              AddFile(infoPlistPath, name + "/Supporting Files/Info.plist", PBXSourceTree.Group);
  
              return newTarget.guid;
          }
  
          public string BuildConfigByName(string targetGuid, string name)
          {
              PBXNativeTarget target = nativeTargets[targetGuid];
              foreach (string guid in configs[target.buildConfigList].buildConfigs)
              {
                  var buildConfig = buildConfigs[guid];
                  if (buildConfig != null && buildConfig.name == name)
                      return buildConfig.guid;
              }
              return null;
          }
  
          // Adds an item to a build property that contains a value list. Duplicate build properties
          // are ignored. Values for name "LIBRARY_SEARCH_PATHS" are quoted if they contain spaces.
          public void AddBuildProperty(string targetGuid, string name, string value)
          {
              PBXNativeTarget target = nativeTargets[targetGuid];
              foreach (string guid in configs[target.buildConfigList].buildConfigs)
                  buildConfigs[guid].AddProperty(name, value);
          }
  
          public void AddBuildProperty(string[] targetGuids, string name, string value)
          {
              foreach (string t in targetGuids)
                  AddBuildProperty(t, name, value);
          }
          public void AddBuildPropertyForConfig(string configGuid, string name, string value)
          {
              buildConfigs[configGuid].AddProperty(name, value);
          }
  
          public void AddBuildPropertyForConfig(string[] configGuids, string name, string value)
          {
              foreach (string guid in configGuids)
                  AddBuildPropertyForConfig(guid, name, value);
          }
  
          public void SetBuildProperty(string targetGuid, string name, string value)
          {
              PBXNativeTarget target = nativeTargets[targetGuid];
              foreach (string guid in configs[target.buildConfigList].buildConfigs)
                  buildConfigs[guid].SetProperty(name, value);
          }
          public void SetBuildProperty(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(string[] configGuids, string name, string value)
          {
              foreach (string guid in configGuids)
                  SetBuildPropertyForConfig(guid, name, value);
          }
  
          /// 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, string[] addValues, string[] removeValues)
          {
              PBXNativeTarget target = nativeTargets[targetGuid];
              foreach (string guid in configs[target.buildConfigList].buildConfigs)
                  buildConfigs[guid].UpdateProperties(name, addValues, removeValues);
          }
          public void UpdateBuildProperty(string[] targetGuids, string name, string[] addValues, string[] removeValues)
          {
              foreach (string t in targetGuids)
                  UpdateBuildProperty(t, name, addValues, removeValues);
          }
          public void UpdateBuildPropertyForConfig(string configGuid, string name, string[] addValues, string[] removeValues)
          {
              buildConfigs[configGuid].UpdateProperties(name, addValues, removeValues);
          }
          public void UpdateBuildPropertyForConfig(string[] configGuids, string name, string[] addValues, string[] removeValues)
          {
              foreach (string guid in configGuids)
                  UpdateBuildProperty(guid, name, addValues, removeValues);
          }
  
          /// Replaces '\' with '/'. We need to apply this function to all paths that come from the user
          /// of the API because we store paths to pbxproj and on windows we may get path with '\' slashes
          /// instead of '/' slashes
          private static string FixSlashesInPath(string path)
          {
              if (path == null)
                  return null;
              return path.Replace('\\', '/');
          }
  
          private void BuildCommentMapForBuildFiles(GUIDToCommentMap comments, List<string> guids, string sectName)
          {
              foreach (var guid in guids)
              {
                  var buildFile = buildFiles[guid];
                  if (buildFile != null)
                  {
                      var fileRef = fileRefs[buildFile.fileRef];
                      if (fileRef != null)
                          comments.Add(guid, String.Format("{0} in {1}", fileRef.name, sectName));
                      else
                      {
                          var reference = references[buildFile.fileRef];
                          if (reference != null)
                              comments.Add(guid, String.Format("{0} in {1}", reference.path, sectName));
                      }
                  }
              }
          }
  
          private GUIDToCommentMap BuildCommentMap()
          {
  
gt:             GUIDToCommentMap comments = new GUIDToCommentMap();