from django.db import models
import hashlib, datetime, random
from django.contrib.auth.models import User


def hashref(user):
    return hashlib.sha1('%s %s' %(
        user,
        datetime.datetime.now()
    )).hexdigest()


class Program(models.Model):
    reference = models.CharField(primary_key = True, max_length = 40)
    user = models.ForeignKey(User)
    is_user = models.BooleanField()
    obsolete = models.BooleanField()

    def initialise(self, request, is_user):
        self.reference = hashref(request.user)
        self.user = request.user
        self.is_user = is_user
        self.obsolete = False
        return self

class Configuration(models.Model):
    reference = models.CharField(primary_key = True, max_length = 40)
    size = models.IntegerField()
    cities = models.IntegerField()


class Problem(models.Model):
    reference = models.CharField(primary_key = True, max_length = 40)
    program = models.ForeignKey(Program)
    configuration = models.ForeignKey(Configuration)
    started = models.DateTimeField(auto_now = True)
    completed = models.DateTimeField(null = True, blank = True)

    def initialise(self, request, program, configuration):
        self.reference = hashref(request.user)
        self.program = program
        self.configuration = configuration
        for n in range(configuration.cities):
            city = City()
            city.problem = self
            city.location_x = random.randint(1, configuration.size)
            city.location_y = random.randint(1, configuration.size)
            while len(City.objects.filter(
                problem = self,
                location_x = city.location_x,
                location_y = city.location_y
            )):
                city.location_x = random.randint(1, configuration.size)
                city.location_y = random.randint(1, configuration.size)
            city.save()
        city = self.reveal()
        city.visited = True
        city.save()
        move = Move()
        move.problem = self
        move.location_x, move.location_y = city.location_x, city.location_y
        move.revealed = self.reveal()
        move.save()
        return self

    def reveal(self):
        cities = City.objects.filter(
            problem = self,
            revealed = False,
            visited = False
        )
        if len(cities):
            city = cities[random.randint(0, len(cities) - 1)]
            city.revealed = True
            city.save()
            return city
        else:
            return None

    def status(self, request):
        move = Move.objects.filter(problem = self).order_by('-id')[0]
        if move.revealed:
            move.revealed.relative(move)
        return dict(
            user = self.program.user,
            problem = self,
            move = move,
            moves = Move.objects.filter(problem = self),
            cansee = [c.relative(move) for c in City.objects.filter(
                problem = self,
                location_x__gte = move.location_x - 1, location_x__lte = move.location_x + 1,
                location_y__gte = move.location_y - 1, location_y__lte = move.location_y + 1,
            ).order_by('id')],
            known = [c.relative(move) for c in City.objects.filter(
                problem = self, revealed = True
            ).order_by('id')]
        )


class City(models.Model):
    problem = models.ForeignKey(Problem)
    location_x = models.IntegerField()
    location_y = models.IntegerField()
    revealed = models.BooleanField()
    visited = models.BooleanField()
    class Meta:
        unique_together = [('problem', 'location_x', 'location_y')]

    def relative(self, move):
        def calculate(mp, sp, pos, neg):
            off = mp - sp
            if off < 0:
                return '%s %s' % (-off, neg), neg[0].upper(), -off
            elif off > 0:
                return '%s %s' % (off, pos), pos[0].upper(), off
            return '', None, None
        self.rel_x, self.x_key, self.x_off = calculate(move.location_x, self.location_x, 'north', 'south')
        self.rel_y, self.y_key, self.y_off = calculate(move.location_y, self.location_y, 'west', 'east')
        return self

    def status(self):
        status = dict(
            visited = self.visited
        )
        if self.x_key:
            status.update({self.x_key: self.x_off})
        if self.y_key:
            status.update({self.y_key: self.y_off})
        return status

class Move(models.Model):
    problem = models.ForeignKey(Problem)
    when = models.DateTimeField(auto_now = True)
    location_x = models.IntegerField()
    location_y = models.IntegerField()
    revealed = models.ForeignKey(City, null = True)

    def step(self, direction):
        step = Move()
        step.problem = self.problem
        if direction == 'N':
            step.location_x = self.location_x - 1
        elif direction == 'S':
            step.location_x = self.location_x + 1
        else:
            step.location_x = self.location_x
        if direction == 'E':
            step.location_y = self.location_y + 1
        elif direction == 'W':
            step.location_y = self.location_y - 1
        else:
            step.location_y = self.location_y
        city = City.objects.filter(
            problem = self.problem,
            location_x = step.location_x,
            location_y = step.location_y
        )
        if len(city) and not city[0].visited:
            step.revealed = self.problem.reveal()
            city[0].visited = True
            city[0].revealed = True
            city[0].save()
        step.save()
        if len(City.objects.filter(
            problem = self.problem,
            visited = False
        )) == 0:
            self.problem.completed = datetime.datetime.now()
            self.problem.save()
