from __future__ import print_function
import errno
import json
import os
import shutil
import subprocess
import sys
import tarfile
import plistlib


def ensure_dir(path):
    try:
        os.makedirs(path)
    except OSError as e:
        if e.errno != errno.EEXIST or not os.path.isdir(path):
            raise


def just_do_it(args):
    if not args:
        raise Exception('Not enough args!')
    parts = [[]]
    for arg in args:
        if arg == '__DELIM__':
            parts.append([])
        else:
            parts[-1].append(arg)
    if len(parts) != 3 or len(parts[0]) != 5:
        raise Exception('Bad call')
    bin_name, ibtool_path, main_out, app_name, module_dir = parts[0]
    bin_name = os.path.basename(bin_name)
    inputs, storyboard_user_flags = parts[1:]
    plists, storyboards, signs, nibs, resources, signed_resources, plist_jsons, strings = [], [], [], [], [], [], [], []
    for i in inputs:
        if i.endswith('.plist') or i.endswith('.partial_plist'):
            plists.append(i)
        elif i.endswith('.compiled_storyboard_tar'):
            storyboards.append(i)
        elif i.endswith('.xcent'):
            signs.append(i)
        elif i.endswith('.nib'):
            nibs.append(i)
        elif i.endswith('.resource_tar'):
            resources.append(i)
        elif i.endswith('.signed_resource_tar'):
            signed_resources.append(i)
        elif i.endswith('.plist_json'):
            plist_jsons.append(i)
        elif i.endswith('.strings_tar'):
            strings.append(i)
        else:
            print('Unknown input:', i, 'ignoring', file=sys.stderr)
    if not plists:
        raise Exception("Can't find plist files")
    if not plists[0].endswith('.plist'):
        print("Main plist file can be defined incorretly", file=sys.stderr)
    if not storyboards:
        print("Storyboards list are empty", file=sys.stderr)
    if len(signs) > 1:
        raise Exception("Too many .xcent files")
    app_dir = os.path.join(module_dir, app_name + '.app')
    ensure_dir(app_dir)
    copy_nibs(nibs, module_dir, app_dir)
    replaced_parameters = {
        'DEVELOPMENT_LANGUAGE': 'en',
        'EXECUTABLE_NAME': bin_name,
        'PRODUCT_BUNDLE_IDENTIFIER': 'Yandex.' + app_name,
        'PRODUCT_NAME': app_name,
    }
    replaced_templates = {}
    for plist_json in plist_jsons:
        with open(plist_json) as jsonfile:
            for k, v in json.loads(jsonfile.read()).items():
                replaced_parameters[k] = v
    for k, v in replaced_parameters.items():
        replaced_templates['$(' + k + ')'] = v
        replaced_templates['${' + k + '}'] = v
    make_main_plist(plists, os.path.join(app_dir, 'Info.plist'), replaced_templates)
    link_storyboards(ibtool_path, storyboards, app_name, app_dir, storyboard_user_flags)
    if resources:
        extract_resources(resources, app_dir)
    if signed_resources:
        extract_resources(signed_resources, app_dir, sign=True)
    if strings:
        extract_resources(strings, app_dir, strings=True)
    if not signs:
        sign_file = os.path.join(module_dir, app_name + '.xcent')
        with open(sign_file, 'w') as f:
            f.write(
                '''<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
        <key>com.apple.security.get-task-allow</key>
        <true/>
</dict>
</plist>
            '''
            )
    else:
        sign_file = signs[0]
    sign_application(sign_file, app_dir)
    make_archive(app_dir, main_out)


def is_exe(fpath):
    return os.path.isfile(fpath) and os.access(fpath, os.X_OK)


def copy_nibs(nibs, module_dir, app_dir):
    for nib in nibs:
        dst = os.path.join(app_dir, os.path.relpath(nib, module_dir))
        ensure_dir(os.path.dirname(dst))
        shutil.copyfile(nib, dst)


def make_main_plist(inputs, out, replaced_parameters):
    united_data = {}
    for i in inputs:
        united_data.update(plistlib.readPlist(i))

    def scan_n_replace(root):
        if not isinstance(root, dict):
            raise Exception('Invalid state')
        for k in root:
            if isinstance(root[k], list):
                for i in xrange(len(root[k])):
                    if isinstance(root[k][i], dict):
                        scan_n_replace(root[k][i])
                    elif root[k][i] in replaced_parameters:
                        root[k][i] = replaced_parameters[root[k][i]]
            elif isinstance(root[k], dict):
                scan_n_replace(root[k])
            else:
                if root[k] in replaced_parameters:
                    root[k] = replaced_parameters[root[k]]

    scan_n_replace(united_data)
    plistlib.writePlist(united_data, out)
    subprocess.check_call(['/usr/bin/plutil', '-convert', 'binary1', out])


def link_storyboards(ibtool, archives, app_name, app_dir, flags):
    unpacked = []
    for arc in archives:
        unpacked.append(os.path.splitext(arc)[0] + 'c')
        ensure_dir(unpacked[-1])
        with tarfile.open(arc) as a:
            a.extractall(path=unpacked[-1])
    flags += [
        '--module',
        app_name,
        '--link',
        app_dir,
    ]
    subprocess.check_call(
        [ibtool] + flags + ['--errors', '--warnings', '--notices', '--output-format', 'human-readable-text'] + unpacked
    )


def sign_application(xcent, app_dir):
    subprocess.check_call(
        ['/usr/bin/codesign', '--force', '--sign', '-', '--entitlements', xcent, '--timestamp=none', app_dir]
    )


def extract_resources(resources, app_dir, strings=False, sign=False):
    for res in resources:
        with tarfile.open(res) as tf:
            for tfinfo in tf:
                tf.extract(tfinfo.name, app_dir)
                if strings:
                    subprocess.check_call(
                        ['/usr/bin/plutil', '-convert', 'binary1', os.path.join(app_dir, tfinfo.name)]
                    )
                if sign:
                    subprocess.check_call(
                        ['/usr/bin/codesign', '--force', '--sign', '-', os.path.join(app_dir, tfinfo.name)]
                    )


def make_archive(app_dir, output):
    with tarfile.open(output, "w") as tar_handle:
        for root, _, files in os.walk(app_dir):
            for f in files:
                tar_handle.add(
                    os.path.join(root, f),
                    arcname=os.path.join(os.path.basename(app_dir), os.path.relpath(os.path.join(root, f), app_dir)),
                )


if __name__ == '__main__':
    just_do_it(sys.argv[1:])