#!/opt/dosis2/bin/python3-venv
# -*- encoding: utf8 -*-

import argparse
import re
import sys

import bs4


def load_xml(filename):
    # type: (str) -> bs4.BeautifulSoup
    with open(filename, 'r') as f:
        xml = bs4.BeautifulSoup(f.read(), 'xml')

    return xml


def format_xml(xml, format_type):
    if format_type == 'pretty':
        return xml.prettify()
    else:
        return ''.join([s.strip() for s in str(xml).split('\n')])


def run(args):
    if args.action == 'read':
        for s in load_xml(args.filename).select(args.select):
            if args.text:
                sys.stdout.write(s.text)
            else:
                print((s.prettify()))
    elif args.action == 'change':
        if not args.text:
            raise Exception('Not implemented')
        else:
            xml = load_xml(args.filename)

            # todo: support element,element
            # todo: support element+element
            # todo: support element~element
            # todo: supported operations are:
            # todo:   element element
            # todo:   element>element
            nodes = [xml]
            modifier = None
            for token in args.select.split():
                if token in '>~':
                    modifier = token
                else:
                    if modifier == '>':
                        def _candidate_generator(tag):
                            return tag.children
                    elif modifier == '~':
                        def _candidate_generator(tag):
                            return tag.next_siblings
                    else:
                        def _candidate_generator(tag):
                            return tag.descendants

                    newnodes = []
                    for n in nodes:
                        newnodes.extend(n.select(
                            token,
                            _candidate_generator=_candidate_generator))

                    if not newnodes and args.create:
                        tagname, attr, value = re.match('^(\w+)(?:\[(\w+)=\"(.*)\"\])?', token).groups()
                        for n in nodes:
                            t = xml.new_tag(tagname)
                            if attr is not None and value is not None:
                                t.attrs = {attr: value}
                            n.append(t)
                            newnodes.append(t)

                    nodes = newnodes
                    modifier = None

            for n in nodes:
                n.string = args.value

        if args.output:
            with open(args.output, 'w') as f:
                f.write(format_xml(xml, args.format_type))
        else:
            print((format_xml(xml, args.format_type)))
    else:
        raise Exception('Unknown action')


def get_argument_parser():
    # type: () -> argparse.ArgumentParser
    parser = argparse.ArgumentParser(description='Perform actions on an XML file')
    subparser = parser.add_subparsers()

    def add_change_parser(sp):
        changeparser = sp.add_parser(
            'change',
            help='Change a value based on a select (currently only works with text nodes)')
        changeparser.set_defaults(action='change')

        changeparser.add_argument(
            'filename',
            metavar='/path/to/file.xml',
            help='Path to XML file to read')
        changeparser.add_argument(
            '--output',
            metavar='/path/to/outputfile.xml',
            help='Path to output XML file')
        changeparser.add_argument(
            '--select',
            required=True,
            dest='select',
            metavar='config > tag[attr="something"] > value',
            help='Select data from XML file to set value')
        changeparser.add_argument(
            '--value',
            required=True,
            dest='value',
            metavar='value',
            help='Value to set select data to')
        changeparser.add_argument(
            '--create',
            action='store_true',
            help='Create a non-existing node if one does not exist')
        changeparser.add_argument(
            '--text',
            required=True,
            action='store_true',
            help='Write to text node (this must be set for now)')
        changeparser.add_argument(
            '--format-type',
            choices=['compressed', 'pretty'],
            default='compressed',
            help='Format method for writting xml')

    def add_read_parser(sp):
        readparser = sp.add_parser('read', help='Read data from an XML file')
        readparser.set_defaults(action='read')

        readparser.add_argument(
            'filename',
            metavar='/path/to/file.xml',
            help='Path to XML file to read')
        readparser.add_argument(
            '--select',
            dest='select',
            metavar='config > tag[attr="something"] > value',
            help='Select data from an XML file')
        readparser.add_argument(
            '--text',
            dest='text',
            action='store_true',
            help='Select only the text nodes from the selected nodes')

    add_change_parser(subparser)
    add_read_parser(subparser)
    return parser


if __name__ == '__main__':
    run(get_argument_parser().parse_args())
