#! /usr/bin/env python

from __future__ import print_function

import argparse, re, timeit, vcsn

parser = argparse.ArgumentParser(description='Bench some algorithms.')
parser.add_argument('--only', metavar='RE',
                    type=re.compile, default='.*',
                    help='run only benches whose title is matched by RE')

args = parser.parse_args()

def bench(title, comment, cmd, number=1):
    "Report the best timing of three batches of number runs of cmd."
    if args.only.search(title):
        t = round(min(timeit.repeat(cmd, number=number)), 2)
        if 1 < number:
            comment += ', {}x'.format(number)
        print("{:5.2f}s: {:20s} # {}".format(t, title, comment))

b = vcsn.context('lal_char(abc)_b')

# Check the cost of dyn calls.
bench('b.format("text")', 'b = lal_char(abc)_b',
      lambda: b.format('text'),
      number=100000)

# I/O on ratexps.
e = '(\e+a)' * 500
bench('b.ratexp(e)', 'e = "(\e+a)" * 500', lambda: b.ratexp(e), number=100)
r = b.ratexp(e)
bench('r.format("text")', 'r = b.ratexp("(\e+a)" * 500)',
      lambda: r.format('text'), number=1000)

# Output should be fast.
r = 'a?{500}'
a = b.ratexp(r).standard()
auts = dict()
for fmt in ['dot', 'efsm', 'fado', 'grail', 'tikz']:
    bench('a.format("'+ fmt + '")', 'a = std({})'.format(r), lambda: a.format(fmt))
    auts[fmt] = a.format(fmt)

# Input should be too.
for fmt in ['dot', 'efsm', 'fado']:
    bench('read(s)', 's = {}(std({}))'.format(fmt, r),
          lambda: vcsn.automaton(auts[fmt], fmt))
del auts

# derived_term
e = "(a+b)*b(<2>a+<2>b){150}"
r = vcsn.context('lal_char(ab)_z').ratexp(e)
bench('r.derived_term()', 'r = {}, c = [ab] -> Z'.format(e),
      lambda: r.derived_term())

r = vcsn.context('lal_char(a-z)_z').ratexp(e)
bench('r.derived_term()', 'r = {}, c = [a-z] -> Z'.format(e),
      lambda: r.derived_term())

# linear
r = vcsn.context('lal_char(a-z)_z').ratexp(e)
bench('r.linear()', 'r = {}, c = [a-z] -> Z'.format(e),
      lambda: r.linear())

# standard
e = "(a+b)*b(<2>a+<2>b){20000}"
r = vcsn.context('lal_char(a-z)_z').ratexp(e)
bench('r.standard()', 'r = {}, c = [a-z] -> Z'.format(e),
      lambda: r.standard())

# thompson
e = "(a+b)*b(<2>a+<2>b){20000}"
r = vcsn.context('lan_char(a-z)_z').ratexp(e)
bench('r.thompson()', 'r = {}, c = [a-z]? -> Z'.format(e),
      lambda: r.thompson())

# determinize.
for n in [21, 18]:
    a = vcsn.context('lal_char(abc)_b').ladybird(n)
    bench('a.determinize()', 'a = ladybird({})'.format(n),
          lambda: a.determinize())

n = 18
a = vcsn.context('lal_char(a-zA-Z0-9)_b').ladybird(n)
bench('a.determinize()', 'a = ladybird({}), c = [a-zA-Z0-9] -> B'.format(n),
      lambda: a.determinize())

for n in [13, 14]:
  a = vcsn.context('lal_char(abc)_b').de_bruijn(n)
  bench('a.determinize()', 'a = de_bruijn({})'.format(n),
        lambda: a.determinize())
  bench('a.determinize("weighted")', 'a = de_bruijn({})'.format(n),
        lambda: a.determinize("weighted"))

# eval.
n = 150
# too slow: db = b.ratexp('(a+b)*a(a+b){' + str(n) + '}').derived_term()
a = b.de_bruijn(n)
bench('a.eval("a"*{})'.format(n+1), 'a = de_bruijn({})'.format(n),
      lambda: a.eval('a'*(n+1)), number=1000)

# shortest.
n = 9
# too slow: db = b.ratexp('(a+b)*a(a+b){' + str(n) + '}').derived_term()
a = b.de_bruijn(n)
bench('a.shortest(5)', 'a = de_bruijn({})'.format(n),
      lambda: a.shortest(5))

# sort.
r = "[a-e]?{600}"
a = vcsn.context("lal_char(a-e)_z").ratexp(r).standard()
bench('a.sort()', 'a = std({})'.format(r),
      lambda: a.shortest(5))

# proper.
r = "a?{1200}"
a = vcsn.context("lan_char(a)_b").ratexp(r).thompson()
bench('a.proper()', 'a = thompson({})'.format(r), lambda: a._proper())

# to-ratexp.
r = '[a-d]?{100}'
a = vcsn.context('lal_char(a-d)_b').ratexp(r).standard()
bench('a.ratexp()', 'a = std({})'.format(r), lambda: a.ratexp())

# Other product and power testcases, with more outgoing transitions
# per state.  This stresses much better the new product algorithm.
r = "[a-e]?{50}"
a = vcsn.context("lal_char(a-e)_z").ratexp(r).standard()
bench('a.product(a)', 'a = std({})'.format(r), lambda: a._product([a,a]))
bench('a.shuffle(a)', 'a = std({})'.format(r), lambda: a.shuffle(a))

# infiltration.
r = "[a-e]?{30}"
a = vcsn.context("lal_char(a-e)_z").ratexp(r).standard()
bench('a.infiltration(a)', 'a = std({})'.format(r), lambda: a.infiltration(a))

# power.
r = "[a-e]*b(<2>[a-e])*"
n = 12
a = vcsn.context("lal_char(a-e)_z").ratexp(r).standard()
bench('a & {}'.format(n), 'a = std({})'.format(r), lambda: a & n)

# compose.
r = "['(a,a)'-'(i,z)']{4}"
a = vcsn.context('lat<lal_char(a-z), lal_char(a-z)>_b') \
        .ratexp(r).standard()
bench('a.compose(a)', 'a = std({})'.format(r), lambda: a.compose(a))

r = "['(a,a)'-'(i,z)']{4}"
a = vcsn.context('lat<lan_char(a-z), lan_char(a-z)>_b') \
        .ratexp(r).thompson()
bench('a.compose(a)', 'a = thompson({})'.format(r), lambda: a.compose(a))

# Minimize a big deterministic automaton over booleans.
r = "[a-g]{800}"
a = vcsn.context("lal_char(a-k)_b").ratexp(r).standard()
bench('a.minimize("signature")', 'a = std({0})'.format(r),
      lambda: a.minimize('signature'))

r = "[a-k]{2000}"
a = vcsn.context("lal_char(a-k)_b").ratexp(r).standard()
bench('a.minimize("moore")', 'a = std({0})'.format(r),
      lambda: a.minimize('moore'))

a = vcsn.context("lal_char(a-c)_b").de_bruijn(6).determinize()
bench('a.synchronizing_word()', 'a = de_bruijn(6)',
      lambda: a.synchronizing_word())

# reduce.
r = "[a-g]{300}"
a = vcsn.context("lal_char(a-k)_z").ratexp(r).standard()
bench('a.reduce()', 'a = std({0}), c = [a-k] -> Z'.format(r),
      lambda: a.reduce())
r = "[a-g]{300}"
a = vcsn.context("lal_char(a-k)_q").ratexp(r).standard()
bench('a.reduce()', 'a = std({0}), c = [a-k] -> Q'.format(r),
      lambda: a.reduce())
