#! /usr/bin/env python
# -*- coding: utf-8 -*-

from pattern import Pattern
from command import Command, CommandOutputParser
import pdb

class LocalSearch(object):
    def __init__(self, model, query):
        self.model = model
        self.query = query
        self.time = 0.0
        self.abstractor = pdb.Abstractor()

    def evaluate(self, pattern):
        abstraction = self.abstractor.gen_abstraction(self.model, pattern)
        cmd = Command("MCTA -o2 -h5 --eval-initial %s %s" % (abstraction, self.query))
        self.time += cmd.parser.sys_time + cmd.parser.usr_time
        return cmd.parser.initial

    def successors(self, pattern):
        pat = [p for p in pattern]
        # random.shuffle(pat)
        for v, var in enumerate(pat):
            if var not in self.clocks_and_chans:
                yield Pattern(pattern=pat[:v] + pat[v+1:])

    def improve(self, pattern):
        self.procs_and_ints = Pattern(self.model, chans=0, clocks=0)
        self.clocks_and_chans = Pattern(self.model, procs=0, ints=0)
        self.initial = self.evaluate(pattern)
        res = self._search(pattern)
        print "pattern selection: %7.2f s" % (self.time + self.abstractor.time)
        return res

    def _search(self, pattern):
        for succ in self.successors(pattern):
            if self.evaluate(succ) == self.initial:
                return self._search(succ)
        return pattern

def version_0(model, query):
    search = LocalSearch(model, query)
    pattern = Pattern(model)
    msg = "Initial pattern : nr. vars %5d, quality %5d" % (len(pattern), search.evaluate(pattern))
    print msg
    return pattern

def version_L(model, query):
    search = LocalSearch(model, query)
    pattern = search.improve(Pattern(model))
    msg = "L  pattern      : nr. vars %5d, quality %5d" % (len(pattern), search.evaluate(pattern))
    print msg
    return pattern

def version_S(model, query):
    search = LocalSearch(model, query)
    abstr = pdb.Abstractor()
    pattern = Pattern(model) - abstr.get_safe_vars(model)
    msg = "S  pattern      : nr. vars %5d, quality %5d" % (len(pattern), search.evaluate(pattern))
    print msg
    return pattern

def version_SL(model, query):
    search = LocalSearch(model, query)
    abstr = pdb.Abstractor()
    pattern = search.improve(Pattern(model) - abstr.get_safe_vars(model))
    msg = "SL pattern      : nr. vars %5d, quality %5d" % (len(pattern), search.evaluate(pattern))
    print msg
    return pattern

def version_LS(model, query):
    search = LocalSearch(model, query)
    abstr = pdb.Abstractor()
    pattern = search.improve(Pattern(model)) - abstr.get_safe_vars(model)
    msg = "LS pattern      : nr. vars %5d, quality %5d" % (len(pattern), search.evaluate(pattern))
    print msg
    return pattern

if __name__ == "__main__":
    from optparse import OptionParser
    import re

    def generate_pattern(model, query):
        if options.init:
            return version_0(model, query)
        elif options.safe:
            return version_S(model, query)
        elif options.local:
            return version_L(model, query)
        elif options.safe_local:
            return version_SL(model, query)
        elif options.local_safe:
            return version_LS(model, query)
        return Pattern()

    def greedy_with_pdb(model, query, pdbfile):
        cmd = Command("MCTA -o2 -h4 --pdb=%s %s %s" % (pdbfile, model, query))
        print "search %7.2f s" % (cmd.parser.sys_time + cmd.parser.usr_time)
        print "states: %8d, time: %7.2f, trace: %7d" % (cmd.parser.states, cmd.parser.usr_time + cmd.parser.sys_time, cmd.parser.trace)

    def astar_with_pdb(model, query, pdbfile):
        cmd = Command("MCTA -o3 -h4 --pdb=%s %s %s" % (pdbfile, model, query))
        print "search %7.2f s" % (cmd.parser.sys_time + cmd.parser.usr_time)
        print "states: %8d, time: %7.2f, trace: %7d" % (cmd.parser.states, cmd.parser.usr_time + cmd.parser.sys_time, cmd.parser.trace)


    op = OptionParser(usage="%prog [options] <model file> <query file>")
    # misc options
    op.add_option("-d", action="store_true", default=False,
                  help="enable logging", dest="debug")
    op.add_option("-v", action="store_true", default=False,
                  help="be verbose", dest="verbose")
    # search methods
    op.add_option("--astar", action="store_true", default=False,
                  help="do an A* search", dest="astar")
    op.add_option("--greedy", action="store_true", default=False,
                  help="do a greedy search", dest="greedy")
    # pattern selection methods
    op.add_option("--init", action="store_true", default=False,
                  help="safe abstraction before local search", dest="init")
    op.add_option("--sl", action="store_true", default=False,
                  help="safe abstraction before local search", dest="safe_local")
    op.add_option("--ls", action="store_true", default=False,
                  help="local search before safe abstraction", dest="local_safe")
    op.add_option("--l", action="store_true", default=False,
                  help="only local search", dest="local")
    op.add_option("--s", action="store_true", default=False,
                  help="only safe abstraction", dest="safe")

    options, args = op.parse_args()
    if options.__dict__.values() == 9*[False]:
        options.astar = True
        options.safe_local = True
    if len(args) != 2:
        print "Error: two input files are required."
        logger.error("wrong number of provided input files")
        exit(1)
    model, query = args

    pattern = generate_pattern(model, query)
    pdb_builder = pdb.PDBBuilder()
    pdb = pdb_builder.gen_pdb(model, query, pattern)
    if options.astar:
        astar_with_pdb(model, query, pdb)
    elif options.greedy:
        greedy_with_pdb(model, query, pdb)
