#!/opt/dosis2/bin/python3-venv
import argparse
import functools
import math
import operator
import sys
import typing


stdindata = None


class ProgrammerError(Exception):
    pass


def __raise_programming_error():
    raise ProgrammerError('you have encountered an error in programming, arguments: "%s"' % (' '.join(sys.argv),))


def get_stdin_value():
    # type: () -> str
    global stdindata
    if stdindata is None:
        stdindata = sys.stdin.read()
        return stdindata
    else:
        return stdindata


def get_float_value(val):
    global stdindata
    # type: (typing.Union[int, float, str]) -> float
    if isinstance(val, str):
        if val == '-':
            return float(get_stdin_value())
        else:
            return float(val)
    else:
        return float(val)


parser = argparse.ArgumentParser(description='Support simple math operations on the command line')
subparsers = parser.add_subparsers()

# max parser
maxparser = subparsers.add_parser('max', help='Get the max of multiple numbers')
maxparser.set_defaults(action='max')
maxparser.add_argument('args', nargs='+', type=get_float_value, metavar='1', help='Arguments get the max of')

# min parser
minparser = subparsers.add_parser('min', help='Get the min of multiple numbers')
minparser.set_defaults(action='min')
minparser.add_argument('args', nargs='+', type=get_float_value, metavar='1', help='Arguments get the min of')

# sum parser
addparser = subparsers.add_parser('sum', help='Get the sum of multiple numbers')
addparser.set_defaults(action='sum')
addparser.add_argument('args',
                       nargs='+',
                       type=get_float_value,
                       metavar='1',
                       help='Arguments get the sum of')

# difference parser
differenceparser = subparsers.add_parser('difference', help='Get the difference of multiple numbers')
differenceparser.set_defaults(action='difference')
differenceparser.add_argument('args',
                              nargs='+',
                              type=get_float_value,
                              metavar='1',
                              help='Arguments get the difference of')

# product parser
productparser = subparsers.add_parser('product', help='Get the product of multiple numbers')
productparser.set_defaults(action='product')
productparser.add_argument('args',
                           nargs='+',
                           type=get_float_value,
                           metavar='1',
                           help='Arguments get the product of')

# quotient parser
quotientparser = subparsers.add_parser('quotient', help='Get the quotient of multiple numbers')
quotientparser.set_defaults(action='quotient')
quotientparser.add_argument('args',
                            nargs='+',
                            type=get_float_value,
                            metavar='1',
                            help='Arguments get the quotient of')

# round parser
roundparser = subparsers.add_parser('round', help='Get the rounded, ceil, or floored number to the nearest n-th place')
roundparser.set_defaults(action='round')
roundparser.add_argument('value', type=get_float_value, help='value to round')
roundparser.add_argument('--places', type=int, default=0, help='how many places to round/ceil/floor to')
roundparser.add_argument('--as-type',
                         type=str,
                         default='float',
                         choices=['int', 'float'],
                         help='Choose between int or float')
roundtypegroup = roundparser.add_mutually_exclusive_group(required=False)
roundtypegroup.add_argument('--floor', action='store_true', help='floor instead of rounding')
roundtypegroup.add_argument('--ceil', action='store_true', help='ceil instead of rounding')

try:
    args = parser.parse_args()
    if args.action == 'max':
        print((max(args.args)))
    elif args.action == 'min':
        print((min(args.args)))
    elif args.action == 'sum':
        print((sum(args.args)))
    elif args.action == 'difference':
        print((functools.reduce(operator.sub, args.args)))
    elif args.action == 'product':
        print((functools.reduce(operator.mul, args.args)))
    elif args.action == 'quotient':
        print((functools.reduce(operator.truediv, args.args)))
    elif args.action == 'round':
        def choose_type(value, totype):
            # type: (typing.Union[int, float], str) -> typing.Union[int, float]
            if totype == 'int':
                return int(value)
            elif totype == 'float':
                return float(value)
            else:
                __raise_programming_error()

        if args.floor:
            print((choose_type(
                math.floor(args.value * (10 ** args.places)) / (10 ** args.places),
                args.as_type
            )))
        elif args.ceil:
            print((choose_type(
                math.ceil(args.value * (10 ** args.places)) / (10 ** args.places),
                args.as_type
            )))
        else:
            print((choose_type(
                round(args.value, args.places),
                args.as_type
            )))
    else:
        __raise_programming_error()
finally:
    pass
