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

# Copyright 2010-2011, RTLCores. All rights reserved.
# http://rtlcores.com
# See LICENSE.txt

import sys,os
from optparse import OptionParser
import re
import traceback
import logging

import simshop
from simshop import SimShopCfg
from simshop import ScoreBoard, SimCfg, TestFind
from simshop import Exceptions
from simshop import builders
from simshop.builders.IcarusVerilog import IcarusVerilog
from simshop import EmailScoreBoard


__author__ = "Tim Weaver - RTLCores"
__version__ = simshop.__version__


if __name__ == '__main__':
    LEVELS = {
                'debug'   : logging.DEBUG,
                'info'    : logging.INFO,
                'warning' : logging.WARNING,
                'error'   : logging.ERROR,
                'critical': logging.CRITICAL,
             }

#==============================================================================
# Option Parsing
#==============================================================================
    parser = OptionParser(usage="%prog [options] [path_to/variant/<testname>]",
        version="%s" % (__version__))
#    parser.add_option("--init",
#                        action="store_true",
#                        dest="init",
#                        help="generate an example variant directory")
    parser.add_option("-l", "--list-tests",
                        action="store_true",
                        dest="list_tests",
                        help="list all available tests")
    parser.add_option("-n", "--dry-run",
                        action="store_true",
                        dest="dry_run",
                        help="print out the commands that would be executed, but do not execute them")
    parser.add_option("-c", "--compile-only",
                        action="store_true",
                        dest="compile_only",
                        help="compile the simulation but don't run it")
    parser.add_option("-d", "--dumpon",
                        action="store_true",
                        dest="dumpon",
                        help="enable dumping of waveform. This is a convenience option for -pDUMPON")
    parser.add_option("-v", "--verbose",
                        action="store_true",
                        dest="verbose",
                        help="display verbose error messages")
    parser.add_option("-D", "--defines",
                        action="append",
                        dest="defines",
                        help="pass in extra defines")
    parser.add_option("-p", "--plusarg",
                        action="append",
                        dest="plusargs",
                        default=[],
                        help="""pass plusargs to the simulation
                        sim -pDUMPON <testname>""")
    parser.add_option("-o", "--output-file",
                        dest="output_file",
                        metavar="FILE",
                        help="""store the scoreboard report to pickle FILE""")
    parser.add_option("--rc",
                        dest="rc_file",
                        default = None,
                        metavar="FILE",
                        help="""parse the resource file FILE""")
    parser.add_option("--email",
                        action="store_true",
                        dest="send_email",
                        default=False,
                        help="""email the results using settings from one of the standard resource files or from an rc file given with the --rc option""")
    parser.add_option("--to",
                        action="append",
                        dest="email_recipients",
                        metavar="RECIPIENT",
                        default=[],
                        help="""send email to the RECIPIENT. Multiple --to can be used to specifiy more recipients.""")
    parser.add_option("--subject",
                        metavar='"SUBJECT"',
                        dest="email_subject",
                        default=None,
                        help="""change the subject of the email. \"My informative subject - $status\"""")
    parser.add_option("--debug",
                        dest="debug",
                        default='warning',
                        help="""run in special debug mode. Valid options are:
                        debug, info, warning, error, critical""")

#    parser.add_option("--clean",
#                        action="store_true",
#                        dest="clean",
#                        help="clean the simbuild directory")

    # List available tools, i.e. iverilog, vcs, modelsim
    # List available builders, i.e. IcarusVerilog

    (options, args) = parser.parse_args()

#==============================================================================
# Message logging
#==============================================================================
    logging.getLogger().setLevel(LEVELS[options.debug])
    ch = logging.StreamHandler()
    formatter = logging.Formatter('%(levelname)-8s - %(message)s')
    ch.setFormatter(formatter)
    logging.getLogger().addHandler(ch)

#==============================================================================
# Find rc files
#==============================================================================
    ssc = SimShopCfg.SimShopCfg()
    try:
        successful_files = ssc.readConfigs(options.rc_file)
        if(options.rc_file is not None):
            if(options.rc_file not in successful_files):
                logging.error("Reading of the rc file '%s' was not successfully" % options.rc_file)
                sys.exit(1)
        for file in ssc.rc_files:
            if(file in successful_files):
                logging.info("%s [FOUND]" % file)
            else:
                logging.info("%s [MISSING]" % file)
    except Exceptions.NoConfigFile, info:
        logging.info("Didn't find any of the standard SimShop rc files:")
        for file in ssc.rc_files:
            logging.info("  %s" % file)
#            print "  " + file
    except:
        raise

#==============================================================================
# The fun starts here
#==============================================================================
    if(options.list_tests):
        t = TestFind.TestFind()
        try:
            if(len(args) > 0):
                t.buildTestStruct(args[0])
                (last_path, last_test) = t.listTests()
            else:
                t.buildTestStruct()
                (last_path, last_test) = t.listTests()
            exe = os.path.normpath(sys.argv[0])
            exe = os.path.split(exe)[1]
            print "To run a simulation:"
            print "%s <path_to/variant>/<test>" % exe
            print ""
            print "Example:"
            if(last_path == "."):
                print "    %s %s" % (exe, last_test)
            else:
                print "    %s %s/%s" % (exe, last_path, last_test)
            print ""
        except Exceptions.TestFindError, info:
            print info.error_message
            sys.exit(1)
        sys.exit(0)

    defines = ""
    plusargs = ""
    if(options.defines):
        defines = " ".join("%s" % x for x in options.defines)

    if(options.dumpon):
        options.plusargs.append('DUMPON')

    if(options.plusargs):
        plusargs += " ".join("%s" % x for x in options.plusargs)

    if(len(args) > 0):
        score_board = ScoreBoard.ScoreBoard('Simulation Score')
        score_board.addErrorRegex(re.compile(r'ERROR:'))
        score_board.addWarningRegex(re.compile(r'WARNING:'))
        score_board.setTestBeginRegex(re.compile(r'TEST_BEGIN'))
        score_board.setTestEndRegex(re.compile(r'TEST_END'))

        cfg_list = []
        try:
            for target in args:
                # Make a new sim_cfg for each new target
                # We don't want stale variables from previous runs
                sim_cfg = SimCfg.SimCfg()
                cfg_list.append(sim_cfg)
                try:
                    sim_cfg.verifyTarget(target)
                except Exceptions.InvalidTest, info:
                    print "The test '%s' does not exist. Check your spelling." % info.error_message
                except Exceptions.InvalidPath, info:
                    print "The path '%s' does not exist." % info.error_message
                except Exceptions.NoSimConfigFound, info:
                    print "The path '%s' does not contain a simulation config file." % info.error_message
                except Exceptions.MultipleConfigFiles, info:
                    print "Multiple Config Files"
                    print "I found the following config files"
                    for i in info.error_message:
                        print " %s" % i
                finally:
                    score_board.addVariant(sim_cfg.variant)

            for sim_cfg in cfg_list:
                try:
                    if(not sim_cfg.invalid):
                        sim_cfg.genAutoTest(options.dry_run, True)
                        sim_cfg['defines'] += " " + defines
                        sim_cfg['plusargs'] += " " + plusargs

                        sim = IcarusVerilog(sim_cfg)

                        sim.buildCompCmd()
                        sim.buildSimCmd()
                        if(options.dry_run):
                            for cmd in sim.cmds:
                                print " ".join(cmd)
                            break
                        if(not options.compile_only):
                            try:
                                stdio = sim.run()
                                sim_cfg.run_time = sim.run_time
                            except builders.Exceptions.ProcessFail, info:
                                sim_cfg.not_run = True
                                sim_cfg.error_message = info.error_message
                            finally:
                                pass
                        else:
                            print "--Compile only--"
                            sim.run(0)

                except:
                    raise

        except KeyboardInterrupt:
            print "KeyboardInterrupt Caught... terminating simulation"
            sys.exit(1)
        except SystemExit:
            sys.exit(1)
        except Exception: # TODO - any exception caught here is lost, fix that
            tb = sys.exc_info()[2]
            stack = []
            while tb:
                stack.append(tb.tb_frame)
                tb = tb.tb_next
            traceback.print_exc()
            sys.exit(1)
        finally:
            if(options.compile_only or options.dry_run):
                pass
            else:
                print ""
                for cfg in cfg_list:
                    try:
                        if(cfg.not_run is True):
                            score_board.scores[cfg.variant].incNotRun(cfg.error_message)
                        else:
                            score_board.scoreTestFromCfg(cfg)
                    except Exceptions.LogFileDoesNotExistError, info:
                        print info.error_message
                        if(options.verbose):
                            print info.long_message
                        else:
                            print "(use -v option to print verbose error messages)"

                # Determine longest string for pretty printing the results
                longest = 0
                longest_str = score_board.longestString()

                # Pretty print the test results
                print ""
                error_count = 0
                warning_count = 0
                incomplete_count = 0
                error_count = score_board['error_count']
                warning_count = score_board['warning_count']
                incomplete_count = score_board['incomplete_count']
                total_nodes = score_board['total_nodes']
                tree = score_board.asciiTree(max_level=score_board.max_level, pad=longest_str+4, print_html=False)
                tally = score_board.asciiTally()

                if(options.output_file is not None):
                    score_board.writePickleFile(options.output_file)
                sys.stdout.write(tree)
                sys.stdout.write("\n")
                sys.stdout.write(tally)

                if(options.send_email):
                    try:
                        if(ssc.has_section('email')):
                            if(options.email_subject is not None):
                                ssc.set('email', 'subject', options.email_subject)
                                print "%r" % options.email_subject
                            if(len(options.email_recipients) > 0):
                                print "%r" % options.email_recipients
                                ssc.set('email', 'to', ' '.join(options.email_recipients))

                        esb = EmailScoreBoard.EmailScoreBoard(ssc, score_board)
                        esb.send()
                    except Exceptions.MissingEmailConfigSection, info:
                        print ""
                        print info
                        print "No mail sent!"
    else:
        parser.print_help()
