import json
import requests
import time


class ExplorationTestCase(object):
    '''Wrapper for the test case part of an Exploration.'''

    def __init__(self, names, test_case):
        self._names = names
        # See https://api.codehunt.com/ for documentation on interface TestCase
        self.test_case = test_case
        # one of the following: "Failure", "Inconclusive", "Success"
        self.status = self.test_case['status']
        self.any_exception_or_path_bounds_exceeded =\
                self.test_case['anyExceptionOrPathBoundsExceeded']
        self.summary = self.test_case['summary']
        self.message = self.test_case['message']
        self.exception = self.test_case['exception']
        self.stack_trace = self.test_case['stackTrace']

        if names and test_case['values']:
            self.values_dict = dict(zip(names, test_case['values']))

            self.names = list(names) # copy names because we mutate it
            self.values = test_case['values']

            if 'EXPECTED RESULT' in self.values_dict:
                self.expected = self.values_dict['EXPECTED RESULT']
                del self.values_dict['EXPECTED RESULT']
                idx = self.names.index('EXPECTED RESULT')
                del self.names[idx]
                del self.values[idx]
            else:
                self.expected = None

            if 'YOUR RESULT' in self.values_dict:
                self.actual = self.values_dict['YOUR RESULT']
                del self.values_dict['YOUR RESULT']
                idx = self.names.index('YOUR RESULT')
                del self.names[idx]
                del self.values[idx]
            else:
                self.actual = None

            for name in self.names:
                if ' ' in name:
                    print(name)
        else:
            self.names = None
            self.values = None
            self.actual = None
            self.expected = None

            print(self.test_case)

    def __repr__(self):
        return "%s.%s(%s, %s)" % (type(self).__module__, type(self).__name__,
                                  repr(self._names), repr(self.test_case))

    def __str__(self):
        params = ', '.join(['%s=%s' % (param, value)
                            for (param, value)
                            in zip(self.names, self.values)])
        correct = 'Puzzle(%(params)s)%(expected)s' % {
                'params': params,
                'expected': ' = %s' % self.expected if self.expected else ''
                }
        if self.summary == 'Mismatch':
            return "%(summary)s: %(correct)s (code returned %(actual)s)" % {
                    'summary': self.summary,
                    'actual': self.actual,
                    'correct': correct,
                    }
        elif self.summary == '' and self.status == 'Success':
            return "Success: %(correct)s" % {
                    'correct': correct,
                    }
        elif self.exception:
            return "Exception: %(correct)s (code threw %(exception)s)" % {
                    'correct': correct,
                    'exception': self.exception,
                    }
        elif self.summary == 'path bounds exceeded (path bounds exceeded)':
            return "Inconclusive: %(correct) (path bounds exceeded)"
        else:
            # Shouldn't get here, but say something meaningful if the above
            #   is missing a case.
            return "%s: %s" % (self.status, correct)

def compilation_error_to_string(error):
    return '%(line)d:%(column)d::%(errorNumber)s: %(errorText)s' % error

class Exploration(object):
    '''Wrapper for /api/explorations response.'''

    def __init__(self, attempt, exp):
        self.attempt = attempt
        # See https://api.codehunt.com/ for documentation on
        #   interface Exploration
        self.exp = exp

        self.is_complete = exp['isComplete']
        self.kind = exp['kind']

        self.attempt_compiles = self.kind == 'TestCases'

        if self.attempt_compiles:
            self.has_won = exp['hasWon']
            self.test_cases = [ExplorationTestCase(exp['names'], tc)
                               for tc in exp['testCases']]
            self.errors = None
        else:
            self.has_won = False
            self.test_cases = None

            if self.kind == 'InternalError':
                # Should not happen
                self.errors = [exp['exception']]
            elif self.kind == 'CompilationError':
                self.compilation_errors = exp['errors']
                self.errors = [compilation_error_to_string(error)
                               for error in exp['errors']]
            elif self.kind == 'BadPuzzle':
                self.errors = [exp['description']]
            elif self.kind == 'BadCodingDuel':
                self.errors = exp['errors']
            elif self.kind == 'BadDependency':
                self.errors = exp['referencedTypes']

    def __repr__(self):
        return "%s.%s(%s, %s)" % (type(self).__module__, type(self).__name__,
                                  repr(self.attempt), repr(self.exp))

    def __str__(self):
        if self.kind == 'TestCases':
            return "{Exploration %(kind)s%(won)s [%(test_cases)s]}" % {
                    'kind': self.kind,
                    'test_cases': '; '.join([str(tc)
                                             for tc in self.test_cases]),
                    'won': ' (won)' if self.has_won else ''
                    }
        else:
            return "{Exploration %(kind)s %(errors)s}" % {
                    'kind': self.kind,
                    'errors': self.errors,
                    }

class Translation(object):
    '''Wrapper for /api/translate response.'''

    def __init__(self, attempt, translation):
        self.attempt = attempt
        # Include level so a translation object is a valid attempt for
        #   the Client.explore() method
        self.level = attempt.level
        self.translation = translation

        if translation['kind'] == 'Translated':
            self.success = True
            self.text = translation['program']['text']
            self.language = translation['program']['language']
            self.errors = None
        else:
            self.success = False
            self.text = None
            self.language = None
            self.errors = translation['errors']

    def __repr__(self):
        return "%s.%s(%s, %s)" % (type(self).__module__, type(self).__name__,
                                  repr(self.attempt), repr(self.translation))

    def __str__(self):
        if self.success:
            return '{Translation of %s}' % self.attempt
        else:
            return '{Failed translation of %s: %s}' % \
                    (self.attempt, [compilation_error_to_string(error)
                                    for error in self.errors])

class Client(object):
    '''Client for Code Hunt REST API. See https://api.codehunt.com/ for
        documentation on the API.'''

    base_url = 'https://api.codehunt.com/api'

    def __init__(self, client_id, client_secret):
        '''client_id and client_secret are the Code Hunt REST API equivalent
            of a username and password. If you do not have a client_id and
            client_secret, you can request them from <EMAIL>.'''

        self.client_id = client_id
        self.client_secret = client_secret

        self.headers = self._get_auth_header()

    def _get_auth_header(self):
        resp = requests.post("%s/token" % self.base_url,
                params = { 'grant_type': 'client_credentials',
                           'client_id': self.client_id,
                           'client_secret': self.client_secret })
        token = resp.json()['access_token']

        return { 'Authorization': 'Bearer %s' % token }

    def explore(self, attempt, wait=False):
        '''Perform an exploration on an attempt finding one of three cases:
            1. It has errors.
            2. It is a correct solution.
            3. It is incorrect, along with counterexamples showing that.
            
        Returns a value of type Exploration.'''

        resp = requests.post("%s/explorations" % self.base_url,
                             headers = self.headers,
                data = json.dumps({
                        'program': {
                                'language': attempt.language,
                                'text': attempt.text
                            },
                        'challengeId': attempt.level.challenge_id,
                    }))
        data = resp.json()
        id = data['id']
        get_exp_url = "%s/explorations/%s" % (self.base_url, id)
        data = requests.get(get_exp_url, headers = self.headers).json()
        # Don't wait for computation because any explorations in the
        #   data release should be cached and be available immediately.
        if wait:
            while not data['isComplete']:
                time.sleep(1)
                data = requests.get(get_exp_url, headers = self.headers).json()

        return Exploration(attempt, data)

    def translate(self, attempt):
        '''Translates a Java program to C#. Note that the translation is very
            limited in its Java support. The result of a translation is either
            1. An error due to the code not being valid Java or using
                unsupported features of Java.
            2. The C# translation of the Java program which Code Hunt uses
                internally.

        Returns a value of type Translation.'''

        if attempt.language != "Java":
            raise Exception("Can only translate Java programs.")

        resp = requests.post("%s/translate?language=CSharp" % self.base_url,
                             headers = self.headers,
                data = json.dumps({
                            'language': attempt.language,
                            'text': attempt.text
                    }))
        data = resp.json()

        return Translation(attempt, data)

import json
import requests
import time


class ExplorationTestCase(object):
    '''Wrapper for the test case part of an Exploration.'''

    def __init__(self, names, test_case):
        self._names = names
        # See https://api.codehunt.com/ for documentation on interface TestCase
        self.test_case = test_case
        # one of the following: "Failure", "Inconclusive", "Success"
        self.status = self.test_case['status']
        self.any_exception_or_path_bounds_exceeded =\
                self.test_case['anyExceptionOrPathBoundsExceeded']
        self.summary = self.test_case['summary']
        self.message = self.test_case['message']
        self.exception = self.test_case['exception']
        self.stack_trace = self.test_case['stackTrace']

        if names and test_case['values']:
            self.values_dict = dict(zip(names, test_case['values']))

            self.names = list(names) # copy names because we mutate it
            self.values = test_case['values']

            if 'EXPECTED RESULT' in self.values_dict:
                self.expected = self.values_dict['EXPECTED RESULT']
                del self.values_dict['EXPECTED RESULT']
                idx = self.names.index('EXPECTED RESULT')
                del self.names[idx]
                del self.values[idx]
            else:
                self.expected = None

            if 'YOUR RESULT' in self.values_dict:
                self.actual = self.values_dict['YOUR RESULT']
                del self.values_dict['YOUR RESULT']
                idx = self.names.index('YOUR RESULT')
                del self.names[idx]
                del self.values[idx]
            else:
                self.actual = None

            for name in self.names:
                if ' ' in name:
                    print(name)
        else:
            self.names = None
            self.values = None
            self.actual = None
            self.expected = None

            print(self.test_case)

    def __repr__(self):
        return "%s.%s(%s, %s)" % (type(self).__module__, type(self).__name__,
                                  repr(self._names), repr(self.test_case))

    def __str__(self):
        params = ', '.join(['%s=%s' % (param, value)
                            for (param, value)
                            in zip(self.names, self.values)])
        correct = 'Puzzle(%(params)s)%(expected)s' % {
                'params': params,
                'expected': ' = %s' % self.expected if self.expected else ''
                }
        if self.summary == 'Mismatch':
            return "%(summary)s: %(correct)s (code returned %(actual)s)" % {
                    'summary': self.summary,
                    'actual': self.actual,
                    'correct': correct,
                    }
        elif self.summary == '' and self.status == 'Success':
            return "Success: %(correct)s" % {
                    'correct': correct,
                    }
        elif self.exception:
            return "Exception: %(correct)s (code threw %(exception)s)" % {
                    'correct': correct,
                    'exception': self.exception,
                    }
        elif self.summary == 'path bounds exceeded (path bounds exceeded)':
            return "Inconclusive: %(correct) (path bounds exceeded)"
        else:
            # Shouldn't get here, but say something meaningful if the above
            #   is missing a case.
            return "%s: %s" % (self.status, correct)

def compilation_error_to_string(error):
    return '%(line)d:%(column)d::%(errorNumber)s: %(errorText)s' % error

class Exploration(object):
    '''Wrapper for /api/explorations response.'''

    def __init__(self, attempt, exp):
        self.attempt = attempt
        # See https://api.codehunt.com/ for documentation on
        #   interface Exploration
        self.exp = exp

        self.is_complete = exp['isComplete']
        self.kind = exp['kind']

        self.attempt_compiles = self.kind == 'TestCases'

        if self.attempt_compiles:
            self.has_won = exp['hasWon']
            self.test_cases = [ExplorationTestCase(exp['names'], tc)
                               for tc in exp['testCases']]
            self.errors = None
        else:
            self.has_won = False
            self.test_cases = None

            if self.kind == 'InternalError':
                # Should not happen
                self.errors = [exp['exception']]
            elif self.kind == 'CompilationError':
                self.compilation_errors = exp['errors']
                self.errors = [compilation_error_to_string(error)
                               for error in exp['errors']]
            elif self.kind == 'BadPuzzle':
                self.errors = [exp['description']]
            elif self.kind == 'BadCodingDuel':
                self.errors = exp['errors']
            elif self.kind == 'BadDependency':
                self.errors = exp['referencedTypes']

    def __repr__(self):
        return "%s.%s(%s, %s)" % (type(self).__module__, type(self).__name__,
                                  repr(self.attempt), repr(self.exp))

    def __str__(self):
        if self.kind == 'TestCases':
            return "{Exploration %(kind)s%(won)s [%(test_cases)s]}" % {
                    'kind': self.kind,
                    'test_cases': '; '.join([str(tc)
                                             for tc in self.test_cases]),
                    'won': ' (won)' if self.has_won else ''
                    }
        else:
            return "{Exploration %(kind)s %(errors)s}" % {
                    'kind': self.kind,
                    'errors': self.errors,
                    }

class Translation(object):
    '''Wrapper for /api/translate response.'''

    def __init__(self, attempt, translation):
        self.attempt = attempt
        # Include level so a translation object is a valid attempt for
        #   the Client.explore() method
        self.level = attempt.level
        self.translation = translation

        if translation['kind'] == 'Translated':
            self.success = True
            self.text = translation['program']['text']
            self.language = translation['program']['language']
            self.errors = None
        else:
            self.success = False
            self.text = None
            self.language = None
            self.errors = translation['errors']

    def __repr__(self):
        return "%s.%s(%s, %s)" % (type(self).__module__, type(self).__name__,
                                  repr(self.attempt), repr(self.translation))

    def __str__(self):
        if self.success:
            return '{Translation of %s}' % self.attempt
        else:
            return '{Failed translation of %s: %s}' % \
                    (self.attempt, [compilation_error_to_string(error)
                                    for error in self.errors])

class Client(object):
    '''Client for Code Hunt REST API. See https://api.codehunt.com/ for
        documentation on the API.'''

    base_url = 'https://api.codehunt.com/api'

    def __init__(self, client_id, client_secret):
        '''client_id and client_secret are the Code Hunt REST API equivalent
            of a username and password. If you do not have a client_id and
            client_secret, you can request them from <EMAIL>.'''

        self.client_id = client_id
        self.client_secret = client_secret

        self.headers = self._get_auth_header()

    def _get_auth_header(self):
        resp = requests.post("%s/token" % self.base_url,
                params = { 'grant_type': 'client_credentials',
                           'client_id': self.client_id,
                           'client_secret': self.client_secret })
        token = resp.json()['access_token']

        return { 'Authorization': 'Bearer %s' % token }

    def explore(self, attempt, wait=False):
        '''Perform an exploration on an attempt finding one of three cases:
            1. It has errors.
            2. It is a correct solution.
            3. It is incorrect, along with counterexamples showing that.
            
        Returns a value of type Exploration.'''

        resp = requests.post("%s/explorations" % self.base_url,
                             headers = self.headers,
                data = json.dumps({
                        'program': {
                                'language': attempt.language,
                                'text': attempt.text
                            },
                        'challengeId': attempt.level.challenge_id,
                    }))
        data = resp.json()
        id = data['id']
        get_exp_url = "%s/explorations/%s" % (self.base_url, id)
        data = requests.get(get_exp_url, headers = self.headers).json()
        # Don't wait for computation because any explorations in the
        #   data release should be cached and be available immediately.
        if wait:
            while not data['isComplete']:
                time.sleep(1)
                data = requests.get(get_exp_url, headers = self.headers).json()

        return Exploration(attempt, data)

    def translate(self, attempt):
        '''Translates a Java program to C#. Note that the translation is very
            limited in its Java support. The result of a translation is either
            1. An error due to the code not being valid Java or using
                unsupported features of Java.
            2. The C# translation of the Java program which Code Hunt uses
                internally.

        Returns a value of type Translation.'''

        if attempt.language != "Java":
            raise Exception("Can only translate Java programs.")

        resp = requests.post("%s/translate?language=CSharp" % self.base_url,
                             headers = self.headers,
                data = json.dumps({
                            'language': attempt.language,
                            'text': attempt.text
                    }))
        data = resp.json()

        return Translation(attempt, data)

Wrapper for the test case part of an Exploration. # See https://api.codehunt.com/ for documentation on interface TestCase # one of the following: "Failure", "Inconclusive", "Success" # copy names because we mutate it # Shouldn't get here, but say something meaningful if the above #   is missing a case. Wrapper for /api/explorations response. # See https://api.codehunt.com/ for documentation on #   interface Exploration # Should not happen Wrapper for /api/translate response. # Include level so a translation object is a valid attempt for #   the Client.explore() method Client for Code Hunt REST API. See https://api.codehunt.com/ for         documentation on the API. client_id and client_secret are the Code Hunt REST API equivalent             of a username and password. If you do not have a client_id and             client_secret, you can request them from <EMAIL>. Perform an exploration on an attempt finding one of three cases:             1. It has errors.             2. It is a correct solution.             3. It is incorrect, along with counterexamples showing that.                      Returns a value of type Exploration. # Don't wait for computation because any explorations in the #   data release should be cached and be available immediately. Translates a Java program to C#. Note that the translation is very             limited in its Java support. The result of a translation is either             1. An error due to the code not being valid Java or using                 unsupported features of Java.             2. The C# translation of the Java program which Code Hunt uses                 internally.          Returns a value of type Translation.