#!/usr/bin/env python3

import argparse
from random import gauss, random, sample
import math
import sys

def init(n):
    return "init({})\n".format(n)

def insert(i, j):
    return "insert({0},{1})\n".format(i,j)

def delete(i, j):
    return "delete({0},{1})\n".format(i,j)

def transitive_closure():
    return "transitive closure?\n"

def expand():
    return "expand()\n"

def remove(i):
    return "remove({})\n".format(i)

def calc_mu(a, m, skew):
    r = 1
    t = ((a/m)*r*2 -r*skew)

    # Note:
    # t \in [-r, r]

    #return (t/(1 + math.fabs(t)))
    return math.tanh(t)

def remove_vertex_from_edgelists(edgelist, vertex):
    indices = list()

    idx = 0
    for e in edgelist:
        if e[0] == vertex or e[1] == vertex:
            indices.append(idx)
            #print("Removing ({}, {})".format(e[0], e[1]))

        idx += 1

    for p in range(len(indices) -1, -1, -1):
        idx = indices[p]

        if idx == len(edgelist) - 1:
            edgelist.pop()
        else:
            edgelist[idx] = edgelist.pop()

def main(it, n, s, o, vert_change_chance, vert_change_balance, debug):
    o.write(init(n))

    # Set of vertices
    missing = list()
    edges   = list()

    # The next vertex number we should choose.
    nextvert = n
    vertices = set()

    mapping = {}

    for i in range(n):
        vertices.add(i)
        mapping[i] = i

    for i in range(n):
        for j in range(n):
            if i == j:
                continue

            #e = edge(i, j)
            missing.append((i, j))


    top = len(missing)
    mu = 0
    sigma = 0.4

    inserts = 0
    deletes = 0

    for i in range(it):
        m = len(missing)
        e = len(edges)

        mu = calc_mu(e, top, s/100)

        g = gauss(mu, sigma)

        if debug:
            print("mu: {}, g: {}".format(mu, g))

        # Override if any array is empty.
        if m == 0:
            g = 1
        elif e == 0:
            g = -1

        if random() < vert_change_chance or (e == 0 and m == 0):
            # TODO: Add/remove a vertex with prob.
            add = random() < vert_change_balance

            if add or len(vertices) < n/2:
                v = nextvert
                nextvert += 1

                m = len(vertices)
                mapping[v] = m

                for j in vertices:
                    missing.append((j, v))
                    missing.append((v, j))

                vertices.add(v)

                print(expand(), file=o, end='', flush=False)

            else:
                v = sample(vertices, 1)[0]

                vertices.remove(v)

                remove_vertex_from_edgelists(missing, v)
                remove_vertex_from_edgelists(edges, v)

                for j in vertices:
                    if mapping[j] > mapping[v]:
                        mapping[j] -= 1

                print(remove(mapping[v]), file=o, end='', flush=False)
                del mapping[v]

            continue

        # Choose an operation (delete, insert)
        if g < 0:
            inserts += 1
            # Insert an edge

            idx = math.floor(m*random())

            element = missing[idx];

            missing[idx] = missing[m - 1];
            missing.pop()

            edges.append(element)

            if mapping[element[0]] > len(vertices) -1 or mapping[element[1]] > len(vertices) -1:
                print("Yooooooooo! No go!!!")
                sys.exit()

            print(insert(mapping[element[0]], mapping[element[1]]), file=o, end='', flush=False)

        else:
            deletes += 1
            # Delete an edge
            idx = math.floor(e*random())

            if len(edges) == 0:
                print(vertices)
                print("Nooooooo edges!!")
                sys.exit()
            element = edges[idx];

            edges[idx] = edges[e - 1];
            edges.pop()

            missing.append(element)

            print(delete(mapping[element[0]], mapping[element[1]]), file=o, end='', flush=False)

        print(transitive_closure(), file=o, end='', flush=True)

    if debug:
        print("inserts: {}, deletes: {}, inserts/deletes: {}".format(inserts, deletes, inserts/deletes))


if __name__ == '__main__':
    parser = argparse.ArgumentParser(description="Generate testfiles for dynamic transitive closure")
    parser.add_argument("-n", type=int, help="The amount of edges in the graph G. Default: 50", default=50)

    parser.add_argument("-c", type=float, help="Probability of adding/removing a vertice. Default 0.01", default=0.01)
    parser.add_argument("-b", type=float, help="Probability that a vertex change is a append. Default 0.5", default=0.5)

    parser.add_argument("-o", "--output", type=argparse.FileType('w'), help="Output file. WILL OVERWRITE FILE WIHTOUT ASKING. Default: stdout")
    parser.add_argument("-s", "--saturation", type=int, default=30,
            help="The percentage of edges we want out of all possible edges, or skew of distribution. Default: 30")

    parser.add_argument("-d", "--debug", type=bool, help="Print debug messages", default=False)

    parser.add_argument("iterations", type=int, help="Number of iteratation to generate")

    args = parser.parse_args()

    if args.output:
        o = args.output
    else:
        o = sys.stdout

    main(args.iterations, args.n, args.saturation, o, args.c, args.b, args.debug)
