#!/usr/bin/env python3
import sys, argparse, random

def main():
    parser = argparse.ArgumentParser(description="General generator for a list of integers.")
    parser.add_argument('-n', metavar='N', type=int, required=True, help='number of integers to generate (at least 1)')
    parser.add_argument('-l', metavar='L', type=int, default=0, help='lower bound (inclusive)')
    parser.add_argument('-u', metavar='U', type=int, default=None, help='upper bound (exclusive) default=2*n')
    parser.add_argument('-o', metavar='O', type=str, default="ran", help='options (dash-separated)')
    parser.add_argument('-s', metavar='S', type=int, default=None, help='seed')
    args = parser.parse_args()
    random.seed(args.s)

    nums = int_list_generator(args.n, args.l, args.u, args.o, args.s)
    print(len(nums))
    print(" ".join(str(x) for x in nums))


def int_list_generator(n, lowerbound=0, upperbound=None, options="ran", seed=None):
    # Supported options:
    # - ran (set each value to a random one within (lb, ub))
    # - dis (set each value to a distinct value within (lb, ub) -- note: must have ub - lb >= n)
    # - inc (add increasing value to each entry, with wrap-around)
    # - rev (reverse)
    # - inv (inverse all elements w.r.t lowerbound/upperbound)
    # - shiftX (add X to every entry, (with wrap-around))
    # - mulX (multiply every element by X (with wrap-around))
    # - repX (make x copies -- note, list size will grow)
    # - noiseX (randomly add value between 0 and X to each element)
    # - setMaxX (set X random elements to upperbound-1)
    # - setMinX (set X random elements to lowebound)

    ## Verify that function is called with sensible parameters
    if (type(n) != int or n < 0):
        print(f"Invalid value for n: {n}", file=sys.stderr)
        sys.exit(1)
    if (upperbound == None or upperbound <= lowerbound):
        upperbound = lowerbound+n
    if (type(lowerbound) != int or type(upperbound) != int or lowerbound >= upperbound):
        print(f"Invalid bounds: ({lowerbound}, {upperbound})", file=sys.stderr)
        sys.exit(1)
    if (type(options) != str):
        print("Options is not a string", file=sys.stderr)
        sys.exit(1)

    return int_list_generator_protected(n, lowerbound, upperbound, options, seed)


def int_list_generator_protected(n, lowerbound, upperbound, options, seed):
    random.seed(seed)
    nums = [lowerbound] * n

    for option in options.split('-'):
        
        # ran: Set each value to a random one within lowerbound/upperbound
        if option == "ran":
            for i in range(len(nums)):
                nums[i] = random.randrange(lowerbound, upperbound)

        # dis: Set each value to a distinct value within (lb,ub)
        elif option == "dis":
            if upperbound - lowerbound < len(nums):
                print(f"Can use option 'dis' when upperbound-lowerbound < len(nums)")
                sys.exit(1)
            nums = random.sample(range(lowerbound, upperbound), len(nums))

        # inc: Add values 0, 1, 2, .... to each element (wrapping around within bound as necessary)
        elif option == "inc":
            for i in range(len(nums)):
                nums[i] = lowerbound + ((nums[i] + i - lowerbound) % (upperbound - lowerbound))

        # rev: reverse the list
        elif option == "rev":
            nums = list(reversed(nums))

        # inv: invert the list w.r.t. upperbound and lowerbound
        elif option == "inv":
            for i in range(len(nums)):
                nums[i] = upperbound - 1 - (nums[i] - lowerbound)

        # shiftX: Shift all values by X (wrapping around within boun as necessary)
        elif option.startswith("shift"):
            value = int(option[len("shift"):])
            for i in range(len(nums)):
                nums[i] = lowerbound + ((nums[i] + value - lowerbound) % (upperbound - lowerbound))
        
        # mulX: Multiply all values by X (wrapping around within bounds as necessary)
        elif option.startswith("mul"):
            value = int(option[len("mul"):])
            for i in range(len(nums)):
                nums[i] = lowerbound + ((nums[i] * value - lowerbound) % (upperbound - lowerbound))

        # repX: Repeat all values in array X times (note: will change size of array, no longer N)
        elif option.startswith("rep"):
            value = int(option[len("rep"):])
            currN = len(nums)
            for i in range(currN):
                nums.append(nums[i])

        # mirror: Append a reversed copy of itself minus last item (note: will double-1 size of array, no longer N)
        elif option == "mirror":
            currN = len(nums)
            for i in range(currN - 2, -1, -1):
                nums.append(nums[i])

        # noiseX: Add random value between 0 and X to each element (wrapping around within range as necessary)
        elif option.startswith("noise"):
            value = int(option[len("noise"):])
            for i in range(len(nums)):
                nums[i] = lowerbound + ((nums[i] + random.randrange(value) - lowerbound) % (upperbound - lowerbound))

        # setMaxX: Set X random elements to max value in range
        elif options.startswith("setMax"):
            value = int(option[len("setMax"):])
            if value > len(nums):
                print(f"Found option {option}, but len(nums) is only {len(nums)}", file=sys.stderr)
                sys.exit(1)
            for i in random.sample(range(len(nums)), value):
                nums[i] = upperbound - 1
        
        # setMinX: Set X random elements to min value in range
        elif options.startswith("setMin"):
            value = int(option[len("setMin"):])
            if value > len(nums):
                print(f"Found option {option}, but len(nums) is only {len(nums)}", file=sys.stderr)
                sys.exit(1)
            for i in random.sample(range(len(nums)), value):
                nums[i] = lowerbound
    
    return nums

if __name__ == "__main__":
    main()
