#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import json
from optparse import OptionParser
import sys

import classad


FLATTEN = "FLATTEN"
EVALUATE = "EVALUATE"
LITERAL = "LITERAL"


class ClassadEncoder(json.JSONEncoder):
    def default(self, o):  # pylint:disable=method-hidden
        if isinstance(o, classad.ExprTree):
            return "!!expr:" + str(o)
        else:
            return json.JSONEncoder.default(self, o)


def process_item(k, v, mode=LITERAL, context=None):
    if mode == "LITERAL":
        return k, str(v)
    elif mode == "FLATTEN":
        if not isinstance(context, classad.ClassAd):
            raise TypeError("`context` must be a ClassAd for flattening")
        return k, str(context.flatten(v))
    elif mode == "EVALUATE":
        if not isinstance(context, classad.ClassAd):
            raise TypeError("`context` must be a ClassAd for evaluation")
        try:
            e = context.eval(k)
            if isinstance(e, classad.Value):
                if e == classad.Value.Error:
                    return k, "!!error"
                elif e == classad.Value.Undefined:
                    return k, None
                else:
                    assert False, "shouldn't be other kinds"
            return k, e
        except ValueError:
            return k, str(context.flatten(v))
    else:
        raise ValueError("Invalid mode")


def process_ad(ad, mode, lower=False):
    return dict(
        [process_item(k.lower() if lower else k, v, mode, ad) for k, v in ad.items()]
    )


def parse_ads(inputlist, mode, lower=False):
    """Return a list of dicts taken from classad text.

    inputlist can be a list of single strings or stream objects; streams must be seekable
    """
    return [process_ad(ad, mode, lower) for ad in classad.parseAds(inputlist)]


def map_on_files(function, files):
    for fn in files or ["-"]:
        stream = sys.stdin if fn == "-" else open(fn, "rt")
        seekable = False
        try:
            stream.seek(0, 1)
            seekable = True
        except IOError:
            pass
        if seekable:
            yield function(stream)
        else:
            yield function(stream.read())
        if fn != "-":
            stream.close()


def main(argv):
    parser = OptionParser("%prog [options] [<FILE>...]")
    parser.add_option(
        "-s",
        "--string",
        dest="mode",
        action="store_const",
        const=LITERAL,
        help="Do not evaluate the values, just print the literal strings",
    )
    parser.add_option(
        "-f",
        "--flatten",
        dest="mode",
        action="store_const",
        const=FLATTEN,
        help="Do not evaluate the values, but flatten the expression trees first",
    )
    parser.add_option(
        "-e",
        "--evaluate",
        dest="mode",
        action="store_const",
        const=EVALUATE,
        help="Evaluate the values",
    )
    parser.add_option(
        "-l", "--lower", dest="lower", action="store_true", help="Lowercase the keys"
    )
    parser.set_defaults(mode=EVALUATE)
    opts, args = parser.parse_args(argv[1:])

    classads = list(map_on_files(lambda x: parse_ads(x, opts.mode, opts.lower), args))
    if len(classads) == 1:
        to_print = classads[0]
    else:
        to_print = classads
    print(
        json.dumps(
            to_print,
            indent=4,
            sort_keys=True,
            separators=(", ", ": "),
            cls=ClassadEncoder,
        )
    )


if __name__ == "__main__":
    sys.exit(main(sys.argv))
