#!/usr/bin/python

# ============================================================================
# Copyright (c) 2010-2013 Qualcomm Connected Experiences, Inc.
# All Rights Reserved.
# Proprietary - Qualcomm Connected Experiences, Inc.
# ============================================================================


# Purpose of this post build script is to post process the project.pbxproj file
# generated by Unity to add any additional Libraries, Frameworks or build paths
# needed to build a QCAR app then add calls to the QCAR library into the Unity
# generated C code

# version date 6/6/14

import sys
import os


# Constants
FRAMEWORK_NAME = 0
FRAMEWORK_ID = 1
FRAMEWORK_FILEREFID = 2

RESFILE_NAME = 0
RESFILE_ID = 1
RESFILE_FILEREFID = 2
RESFILE_LASTKNOWNTYPE = 3

# These ids have been generated by creating a project using Xcode then
# extracting the values from the generated project.pbxproj.  The format of this
# file is not documented by Apple so the correct algorithm for generating these
# ids is unknown

AVFOUNDATION_ID = 'CCE8C2AB135C7CDD000D8035'
AVFOUNDATION_FILEREFID = 'CCE8C2AA135C7CDD000D8035'

COREVIDEO_ID = 'CC375CE01316C2C5004F0FDD'
COREVIDEO_FILEREFID = 'CC375CDF1316C2C5004F0FDD'

COREMEDIA_ID = 'CC375CE51316C2D3004F0FDD'
COREMEDIA_FILEREFID = 'CC375CE41316C2D3004F0FDD'

SECURITY_ID = 'CCE8C2BA135C7EA3000D8035'
SECURITY_FILEREFID = 'CCE8C2B9135C7EA3000D8035'

QCARDIR_ID = 'CC9FCA1D1445D76E004F4DC3'
QCARDIR_FILEREFID = 'CC9FCA171445D76E004F4DC3'



# List of all the frameworks to be added to the project
frameworks = [["AVFoundation.framework", AVFOUNDATION_ID, AVFOUNDATION_FILEREFID], \
              ["CoreMedia.framework", COREMEDIA_ID, COREMEDIA_FILEREFID], \
              ["CoreVideo.framework", COREVIDEO_ID, COREVIDEO_FILEREFID], \
              ["Security.framework", SECURITY_ID, SECURITY_FILEREFID]]

# List of data files to be added to the app bundle
resfiles = [["QCAR", QCARDIR_ID, QCARDIR_FILEREFID, 'folder']]



# Adds a line into the PBXBuildFile section
def add_build_file(pbxproj, id, name, fileref):
    subsection = 'Resources'
    if name[-9:] == 'framework':
        subsection = 'Frameworks'
    print "Adding build file " + name + '\n'
    pbxproj.write('\t\t' + id + ' /* ' + name  + ' in ' + subsection + ' */ = {isa = PBXBuildFile; fileRef = ' + fileref +  ' /* ' + name + ' */; };\n')

#Adds a line to the PBXFileReference to add a resource file
def add_res_file_reference(pbxproj, id, name, last_known_file_type):
    print "Adding data file reference " + name + "\n"
    pbxproj.write('\t\t' + id + ' /* ' + name + ' */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = ' + last_known_file_type + '; name = ' + name + '; path = Data/Raw/' + name + '; sourceTree = SOURCE_ROOT; };\n')

# Adds a line into the PBXFileReference section to add a framework
def add_framework_file_reference(pbxproj, id, name):
    print "Adding framework file reference " + name + '\n'
    pbxproj.write('\t\t' + id + ' /* ' + name + ' */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = ' + name + '; path = System/Library/Frameworks/' + name + '; sourceTree = SDKROOT; };\n')

# Adds a line into the PBXFrameworksBuildPhase section
def add_frameworks_build_phase(pbxproj, id, name):
    print "Adding build phase " + name + '\n'
    pbxproj.write('\t\t\t\t' + id + ' /* ' + name + ' in Frameworks */,\n')

# Adds a line into the PBXResourcesBuildPhase section
def add_resources_build_phase(pbxproj, id, name):
    print "Adding build phase " + name + '\n'
    pbxproj.write('\t\t\t\t' + id + ' /* ' + name + ' in Resources */,\n')

# Adds a line into the PBXGroup section
def add_group(pbxproj, id, name):
    print "Add group " + name + '\n'
    pbxproj.write('\t\t\t\t' + id + ' /* ' + name + ' */,\n')

# Returns a list of all the files already in a pbxproj
#    lines - a list of all the lines read in from a pbxproj
def read_existing_files(lines):
    begin_pbxbuildfile_section = False
    existing_files = []
    i = 0
    line = lines[i]
    while line[3:6] != 'End':
        if not begin_pbxbuildfile_section:
            begin_pbxbuildfile_section = (line[3:21] == 'Begin PBXBuildFile')
        else:
            existing_files.append(line.split()[2])
        i = i + 1
        line = lines[i]

    return existing_files


# Processes the given xcode project to add or change the supplied parameters
#   xcodeproj_filename - filename of the Xcode project to change
#   frameworks - list of Apple standard frameworks to add to the project
#   resfiles - list resource files added to the project
def process_pbxproj(xcodeproj_filename, frameworks, resfiles):

    # Open up the file generated by Unity and read into memory as
    # a list of lines for processing
    pbxproj_filename = xcodeproj_filename + '/project.pbxproj'
    pbxproj = open(pbxproj_filename, 'r')
    lines = pbxproj.readlines()
    pbxproj.close()

    # Work out which of the resfiles exist and remove them if they don't, this
    # is because if using frame markers there may not be a qcar-resources.dat
    resfiles = [x for x in resfiles if os.path.exists(xcodeproj_filename + '/../Data/Raw/' + x[RESFILE_NAME])]

    # Next open up an empty project.pbxproj for writing and iterate over the old
    # file copying the original file and inserting anything extra we need
    pbxproj = open(pbxproj_filename, 'w')

    # As we iterate through the list we'll record which section of the
    # project.pbxproj we are currently in
    section = ''

    # We use these booleans to decide whether we have already added the list of
    # build files to the link line.  This is needed because there could be multiple
    # build targets and they are not named in the project.pbxproj
    frameworks_build_added = False
    res_build_added = False

    # Build a list of the files already added to the project.  Then use it to
    # avoid adding anything to the project twice
    existing_files = read_existing_files(lines)
    filtered_frameworks = []
    for framework in frameworks:
        if framework[0] not in existing_files:
            filtered_frameworks.append(framework)
    frameworks = filtered_frameworks
    for resfile in resfiles:
        if resfile[0] in existing_files:
            resfiles.remove(resfile)
    
   
    # Now iterate through the project adding any new lines where needed
    i = 0
    for i in range(0, len(lines)):
        line = lines[i]
        pbxproj.write(line)

        # Each section starts with a comment such as
        # /* Begin PBXBuildFile section */'
        if line[3:8] == 'Begin':
            section = line.split(' ')[2]
            if section == 'PBXBuildFile':
                for framework in frameworks:
                    add_build_file(pbxproj, framework[FRAMEWORK_ID], framework[FRAMEWORK_NAME], framework[FRAMEWORK_FILEREFID])
                for resfile in resfiles:
                    add_build_file(pbxproj, resfile[RESFILE_ID], resfile[RESFILE_NAME], resfile[RESFILE_FILEREFID])

            if section == 'PBXFileReference':
                for framework in frameworks:
                    add_framework_file_reference(pbxproj, framework[FRAMEWORK_FILEREFID], framework[FRAMEWORK_NAME])
                for resfile in resfiles:
                    add_res_file_reference(pbxproj, resfile[RESFILE_FILEREFID], resfile[RESFILE_NAME], resfile[RESFILE_LASTKNOWNTYPE])
    
        if line[3:6] == 'End':
            section = ''
   
        if section == 'PBXFrameworksBuildPhase':
            if line.strip()[0:5] == 'files':
                if not frameworks_build_added:
                    for framework in frameworks:
                        add_frameworks_build_phase(pbxproj, framework[FRAMEWORK_ID], framework[FRAMEWORK_NAME])
                    frameworks_build_added = True

        # The PBXResourcesBuildPhase section is what appears in XCode as 'Link
        # Binary With Libraries'.  As with the frameworks we make the assumption the
        # first target is always 'Unity-iPhone' as the name of the target itself is
        # not listed in project.pbxproj
        if section == 'PBXResourcesBuildPhase':
            if line.strip()[0:5] == 'files':
                if not res_build_added:
                    for resfile in resfiles:
                        add_resources_build_phase(pbxproj,resfile[RESFILE_ID], resfile[RESFILE_NAME])
                    res_build_added = True

        # The PBXGroup is the section that appears in XCode as 'Copy Bundle Resources'. 
        if section == 'PBXGroup':
            if (line.strip()[0:8] == 'children') and (lines[i-2].strip().split(' ')[2] == 'CustomTemplate'):
                for resfile in resfiles:
                    add_group(pbxproj, resfile[RESFILE_FILEREFID], resfile[RESFILE_NAME])
                for framework in frameworks:
                    add_group(pbxproj, framework[FRAMEWORK_FILEREFID], framework[FRAMEWORK_NAME])

        # The PBXShellScriptBuildPhase appears in Xcode 4 as "Run Script", we need to delete the QCAR
        # directory from the app to avoid a duplicate copy
        if section == 'PBXShellScriptBuildPhase':
            if (line.strip()[0:11] == 'shellScript'):
                pbxproj.seek(-3, os.SEEK_CUR)
                pbxproj.write('\\nrm -rf \\"$TARGET_BUILD_DIR/$PRODUCT_NAME.app/Data/Raw/QCAR\\"\";\n')

        # change for Unity 4.2 because header search path needs to be in HEADER_SEARCH_PATHS group
        if section == 'XCBuildConfiguration':
            if line.strip()[0:23] == 'HEADER_SEARCH_PATHS = (':
                pbxproj.write('\t\t\t\t\t\"$(SRCROOT)/Libraries\",\n')

            #add C++11 support by explicitly linking to libc++
            if line.strip()[0:17] == 'OTHER_LDFLAGS = (':
                pbxproj.write('\t\t\t\t\t\"-lc++\",\n')

    pbxproj.close()


# Script start
print "Starting PostProcessBuildPlayer with the following arguments..."

i = 0
for args in sys.argv:
    print str(i) +': ' + args
    i += 1

# Check this is an iOS build before running
if sys.argv[2] == "iPhone": 

    # process the xcode project
    xcodeproj_full_path_name = sys.argv[1] + '/Unity-iPhone.xcodeproj'
    process_pbxproj(xcodeproj_full_path_name, frameworks, resfiles)


