import sys
import time
import trac.core
from interfaces import IAssignment

class EvaluationError(Exception):
    """raise by assignments when something doesn't evaluate"""

class Assignment(trac.core.Component):
    """superclass for assignments"""

    question = 'No question in assignment'

    def evaluate(self, c):
        """override, should perform tests to see if callable 'c' is valid
        
            raise an EvaluationError when validation fails
        """

    def test_timed(self, c):
        """override, return some useful value (float) for execution speed"""

    # internal methods
    
    def test(self, username, client, result, report):
        if result is None:
            report.report_part(username)
            return
        try:
            compiled = compile(result, 'string', 'exec')
        except:
            report.report_error(username, result, *sys.exc_info())
            return
        # we have usable data, perform some tests
        env = {}
        try:
            eval(compiled, env)
        except:
            report.report_error(username, result, *sys.exc_info())
            return
        if not env.has_key('main'):
            report.report_invalid(username, result, 
                                    'no main function defined')
            return
        try:
            self.evaluate(env['main'])
        except EvaluationError, e:
            report.report_invalid(username, result, e)
            return
        except:
            report.report_error(username, result, *sys.exc_info())
            return
        looptime = self.test_timed(env['main'])
        report.report_success(username, result, 
                                self._get_code_size(compiled), 
                                client.server_data['time_spent'], looptime)

    def _get_code_size(self, c):
        """get the size of the compiled code"""
        # XXX really not sure if this is sufficient...
        ret = len(c.co_code)
        ctype = type(c)
        for var in c.co_consts:
            if type(var) == ctype:
                ret += self._get_code_size(var)
        return ret

class HelloWorld(Assignment):
    """simple hello, world example"""

    # disabled, this is nice for testing but not a very cool assignment :|
    trac.core.implements(IAssignment)

    question = "Create a callable that returns the string 'hello, world!'"

    def evaluate(self, c):
        a = c()
        if a != 'hello, world!':
            raise EvaluationError, '%r != "hello, world!"' % a

    def test_timed(self, c):
        """override, return some useful value for execution speed"""
        start = time.clock()
        for i in xrange(1000):
            c()
        return time.clock() - start

class Primes(Assignment):
    """Produce prime numbers"""
    trac.core.implements(IAssignment)

    question = ('Create a function main(n) which returns a list of all\n'
                'prime numbers between 0 and n.')

    primes_up_to_1000 = None
    def evaluate(self, c):
        # XXX can't override __init__, trac.core doesn't seem to like that...
        if self.primes_up_to_1000 is None:
            self.primes_up_to_1000 = p = list(self._primes(1000))
            print 'p:', repr(p)
        ret = c(2) # exclusive!!
        if ret != []:
            raise EvaluationError, ('test failed, main(2) returned %r, '
                                    'not []' % (ret,))
        ret = c(7)
        if ret != [2, 3, 5]:
            raise EvaluationError, ('test failed, main(7) returned %r, ' 
                                    'not [2, 3, 5]' % (ret,))
        ret = c(8)
        if ret != [2, 3, 5, 7]:
            raise EvaluationError, ('test failed, main(8) returned %r, ' 
                                    'not [2, 3, 5, 7]' % (ret,))
        ret = c(1000)
        ret.sort()
        if ret != self.primes_up_to_1000:
            raise EvaluationError, 'test failed, primes < 1000 not produced!'

    def test_timed(self, c):
        """override, return some useful value for execution speed"""
        start = time.clock()
        for i in xrange(100):
            c(1000)
        return time.clock() - start

    def _primes(self, n):
        """very naive and easy to check/debug implementation of the assignment
        """
        for i in xrange(2, n):
            for j in xrange(2, i):
                if i % n == 0:
                    break
            else:
                yield i


