#!/usr/bin/env python3
import sys
import os
import argparse
import tempfile
import shutil
import subprocess
import re
import fileinput
from collections import OrderedDict
import json
import traceback

# Hack to combine two argparse formatters
class CustomFormatter(argparse.ArgumentDefaultsHelpFormatter, argparse.RawDescriptionHelpFormatter):
    pass

parser = argparse.ArgumentParser(
    description='Prepare build/ files for the app build process',
    formatter_class=CustomFormatter)

parser.add_argument('--source-dir', '-s', required=True, metavar='BUILD_DIR', help='Directory to build from')
parser.add_argument('--output-dir', '-o', required=True, metavar='OUTPUT_DIR', help='Directory to write files to')
parser.add_argument('-c', '--channel', default='source', help='channel to add to dev build version number (e.g., "beta" for "5.0-beta.3+a5f28ca8"), or "release" or "source" to skip')
parser.add_argument('--commit-hash', '-m', metavar='HASH', help='Commit hash (required for non-release builds)')

args = parser.parse_args()

def main():
    try:
        app_root_dir = os.path.dirname(os.path.dirname(os.path.realpath(__file__)))
        
        lastrev_dir = os.path.join(app_root_dir, 'lastrev')
        if not os.path.exists(lastrev_dir):
            os.mkdir(lastrev_dir)
        tmp_dir = os.path.join(app_root_dir, 'tmp')
        
        if args.commit_hash:
            commit_hash = args.commit_hash[0:9]
        elif args.channel != "release":
            raise Exception("--commit-hash must be specified for non-release builds")
        
        src_dir = args.source_dir
        if not os.path.isdir(src_dir):
            raise Exception(src_dir + " is not a directory")
        
        output_dir = args.output_dir
        if not os.path.isdir(output_dir):
            raise Exception(output_dir + " is not a directory")
        if os.listdir(output_dir):
            raise Exception(output_dir + " is not empty")
        
        log("Using source directory of " + src_dir)
        os.chdir(src_dir)
        
        if not os.path.exists('version'):
            raise FileNotFoundError("version file not found in {0}".format(src_dir))
        
        # Extract version number from version file
        with open('version') as f:
            rdf = f.read()
        m = re.search('([0-9].+)\\.SOURCE', rdf)
        if not m:
            raise Exception("Version number not found in version file")
        version = m.group(1)
        
        # Remove tmp build directory if it already exists
        if os.path.exists(tmp_dir):
            shutil.rmtree(tmp_dir)
        os.mkdir(tmp_dir)
        
        tmp_src_dir = os.path.join(tmp_dir, 'zotero')
        
        # Export a clean copy of the source tree -- with Python on Windows, where rsync,
        # if present at all, doesn't handle Windows-style paths
        if os.name == 'nt' or not shutil.which('rsync'):
            shutil.copytree(
                '.',
                tmp_src_dir,
                ignore=shutil.ignore_patterns('.*', '#*', 'package.json', 'package-lock.json')
            )
        else:
            subprocess.check_call([
                'rsync',
                '-aL',
                # Exclude hidden files
                '--exclude', '.*',
                '--exclude', '#*',
                '--exclude', 'package.json',
                '--exclude', 'package-lock.json',
                '.' + os.sep,
                tmp_src_dir + os.sep
            ])
        
        # Make sure the copy worked
        d = os.path.join(tmp_src_dir, 'chrome')
        if not os.path.isdir(d):
            raise FileNotFoundError(d + " not found")
        
        # Delete CSL locale support files
        csl_dir = os.path.normpath(tmp_src_dir + '/chrome/content/zotero/locale/csl/')
        for root, dirs, files in os.walk(csl_dir, topdown=False):
            for name in files:
                if not name.endswith('.xml') and name != 'locales.json':
                    os.remove(os.path.join(root, name))
            for name in dirs:
                d = os.path.join(root, name)
                if not os.listdir(d):
                    os.rmdir(d)
        
        # Delete styles build script
        os.remove(os.path.join(tmp_src_dir, 'styles', 'update'))
        
        translators_dir = os.path.join(tmp_src_dir, 'translators')
        
        # Move deleted.txt out of translators directory
        f = os.path.join(translators_dir, 'deleted.txt')
        if os.path.exists(f):
            shutil.move(f, tmp_src_dir)
        
        # Build translator index
        index = OrderedDict()
        for fn in sorted((fn for fn in os.listdir(translators_dir)), key=str.lower):
            if not fn.endswith('.js'):
                continue
            with open(os.path.join(translators_dir, fn), 'r', encoding='utf-8') as f:
                contents = f.read()
            # Parse out the JSON metadata block
            m = re.match(r'^\s*{[\S\s]*?}\s*?[\r\n]', contents)
            if not m:
                raise Exception("Metadata block not found in " + f.name)
            metadata = json.loads(m.group(0))
            index[metadata["translatorID"]] = {
                "fileName": fn,
                "label": metadata["label"],
                "lastUpdated": metadata["lastUpdated"]
            }
        
        # Write translator index as JSON file
        with open(os.path.join(tmp_src_dir, 'translators.json'), 'w', encoding='utf-8') as f:
            json.dump(index, f, indent=True, ensure_ascii=False)
        
        version_file = os.path.join(tmp_src_dir, 'version')
        
        log('')
        log_line()
        log('Original version:\n')
        dump_file(version_file)
        
        # Modify version as necessary
        
        # The dev build revision number is stored in lastrev/{version}-{channel}.
        #
        # If we're including it, get the current version number and increment it.
        if args.channel not in ["release", "source"]:
            lastrev_file = os.path.join(
                lastrev_dir, '{0}-{1}'.format(version, args.channel)
            )
            if not os.path.exists(lastrev_file):
                with open(lastrev_file, 'w') as f:
                    f.write("0")
                    rev = 1
            else:
                with open(lastrev_file, 'r') as f:
                    rev = f.read()
                    rev = int(rev if rev else 0) + 1
        
        if args.channel == "release":
            rev_sub_str = ""
        elif args.channel == "source":
            rev_sub_str = ".SOURCE.{0}".format(commit_hash)
        else:
            rev_sub_str = "-{0}.{1}+{2}".format(args.channel, str(rev), commit_hash)
        # Update version
        for line in fileinput.FileInput(version_file, inplace=1):
            line = line.replace('.SOURCE', rev_sub_str)
            print(line, file=sys.stdout, end='')
        
        log('Modified version:\n')
        dump_file(version_file)
        log('')
        log_line()
        
        # Move source files to output directory
        os.rmdir(output_dir)
        shutil.move(tmp_src_dir, output_dir)
        
        # Update lastrev file with new revision number
        if args.channel not in ["release", "source"]:
            with open(lastrev_file, 'w') as f:
                f.write(str(rev))
        
        return 0
    
    except Exception as err:
        sys.stderr.write("\n" + traceback.format_exc())
        return 1
    
    # Clean up
    finally:
        if 'tmp_src_dir' in locals() and os.path.exists(tmp_src_dir):
            shutil.rmtree(tmp_src_dir)


def dump_file(f):
    with open(f, 'r') as f:
        log(f.read())


def log(msg):
    print(msg, file=sys.stdout)


def log_line():
    log('======================================================\n\n')

if __name__ == '__main__':
    sys.exit(main())
