#!/usr/bin/env python
# -*- coding: utf-8 -*-
# Copyright © 2017-2019 Xavier G. <xavier.boobank@kindwolf.org>
# This work is free. You can redistribute it and/or modify it under the
# terms of the Do What The Fuck You Want To Public License, Version 2,
# as published by Sam Hocevar. See the COPYING file for more details.
"""
boobank-cacher runs a list of boobank commands defined in its configuration
file. For each command, it has the ability to inject simple, additional data to
the result before saving it to a JSON file.
That JSON file can then act as a local cache, which other tools can leverage
for various purposes without emitting too many requests to bank websites.
"""

import os
import re
import json
import copy
import time
import argparse
import subprocess
from boobank_utils import mkdir, DEFAULT_CONF_DIR

def get_conf(conf_path):
    """
    Return the contents of the boobank-cacher configuration.
    """
    conf_path = os.path.expanduser(conf_path)
    with open(conf_path, 'r') as filedesc:
        return json.load(filedesc)

def run_boobank(conf):
    """
    Pipe the commands found in the given boobank-cacher configuration to a
    single boobank process and return their results.
    """
    results = copy.deepcopy(conf)
    # Ensure there is at least one command to run:
    commands = results.get('commands', [])
    if not commands:
        return results
    boobank_path = os.path.expanduser(conf.get('boobank_path', 'boobank'))
    boobank_command = [boobank_path, '--formatter=json']
    boobank_process = subprocess.Popen(boobank_command, stdin=subprocess.PIPE,
                                       stdout=subprocess.PIPE, close_fds=True, bufsize=-1)
    # Send all commands at once:
    for command in commands:
        boobank_process.stdin.write(command['command'] + "\n")
    boobank_process.stdin.write("quit\n")
    boobank_process.stdin.flush()
    boobank_process.stdin.close()
    # Parse output to extract relevant lines only:
    out_time = time.time()
    out_lines = []
    while True:
        out_line = boobank_process.stdout.readline()
        if not out_line:
            break
        rem = re.search(r'^boobank>\s*(.+)$', out_line)
        if rem:
            out_lines.append(rem.group(1))
    for i in xrange(len(commands)):
        out_data = json.loads(out_lines[i])
        commands[i]['result'] = {'timestamp': out_time, 'data': out_data}
    return results

def process_additions(results):
    """
    Inject additional data described in the boobank-cacher configuration to the
    given results.
    """
    for command in results.get('commands', []):
        additions = command.get('additions', {})
        result = command.get('result', {})
        if not additions or not result or 'data' not in result:
            continue

        processed_items = []
        for item in result.get('data', []):
            processed_items.append(item)
            item_id = item.get('id')
            if item_id is not None and item_id in additions:
                processed_items.append(additions.get(item_id))
        result['data'] = processed_items
    return results

def write_cache(results):
    """
    Write the given results to cache files as described in the boobank-cacher
    configuration.
    """
    for command in results.get('commands', []):
        if 'result' not in command or 'cache_path' not in command:
            continue
		# There is no point caching data-less results:
        if 'data' not in command['result'] or not command['result']['data']:
            continue
        cache_path = os.path.expanduser(command['cache_path'])
        mkdir(os.path.dirname(cache_path), 0700)
        with open(cache_path, 'w') as filedesc:
            json.dump(command['result'], filedesc, indent=4)

def parse_args():
    """
    Parse command-line arguments.
    """
    description = 'Cache the output of boobank commands to JSON files.'
    args_parser = argparse.ArgumentParser(description=description)
    default_conf = os.path.join(DEFAULT_CONF_DIR, 'cacher.json')
    args_parser.add_argument('-c', '--config', default=default_conf,
                             help='configuration file to use')
    return args_parser.parse_args()

def main():
    """
    Main function.
    """
    args = parse_args()
    conf = get_conf(args.config)
    results = run_boobank(conf)
    process_additions(results)
    write_cache(results)

if __name__ == '__main__':
    main()