# Copyright 2014 Bevbot LLC, All Rights Reserved
#
# This file is part of the Pykeg package of the Kegbot project.
# For more information on Pykeg or Kegbot, see http://kegbot.org/
#
# Pykeg is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 2 of the License, or
# (at your option) any later version.
#
# Pykeg is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Pykeg.  If not, see <http://www.gnu.org/licenses/>.

import json
import os.path
import random
import datetime
from django.conf import settings
from collections import deque
from uuid import uuid1

from django.contrib.auth.hashers import make_password
from django.core.files import File
from django.core.management.base import BaseCommand
from django.core.management.base import CommandError
from django.db import transaction
from django.utils import timezone

from pykeg.contrib.demomode.views import random_item
from pykeg.backend import get_kegbot_backend
from pykeg.core import defaults
from pykeg.core import models


NUM_SESSIONS = 30
MINUTES_IN_DAY = 60 * 24

MINIMUM_POUR_SIZE_ML = 100
MAXIMUM_POUR_SIZE_ML = 450

# Predictably generate same demo data.
RANDOM_SEED = 42
START_DATE = datetime.datetime(2014, 10, 1)
random.seed(RANDOM_SEED)

"""Generates a bunch of fake drinks from a configuration."""


class LoadDemoDataCommand(BaseCommand):
    args = '<demo_data_dir>'
    help = 'Inserts demo data into the database'

    def handle(self, *args, **options):
        if len(args) < 1:
            raise CommandError('Must specify demo data directory')

        data_dir = os.path.abspath(args[0])
        demo_data = self.load_demo_data(data_dir)

        settings.CELERY_ALWAYS_EAGER = True

        with transaction.atomic():
            if models.User.objects.all().count() or models.Drink.objects.all().count():
                raise CommandError('Database is already loaded.')

            defaults.set_defaults(set_is_setup=True, create_controller=True)

            # Install demo data.
            for username in demo_data['drinkers']:
                user = models.User.objects.create_superuser(username=username, email=None, password=username)
                pw = make_password(username, salt='demo')
                user.password = pw
                user.date_joined = START_DATE
                user.save()

                picture_path = os.path.join(data_dir, 'pictures', 'drinkers', username, 'mugshot.png')
                if os.path.exists(picture_path):
                    p = self.create_picture(picture_path, user=user)
                    user.mugshot = p
                    user.save()

            for beverage in demo_data['beverages']:
                brewer, _ = models.BeverageProducer.objects.get_or_create(name=beverage['brewer_name'])
                beer, _ = models.Beverage.objects.get_or_create(name=beverage['name'], beverage_type='beer',
                    producer=brewer, style=beverage['style'])

                picture_path = beverage.get('image')
                if picture_path:
                    picture = self.create_picture(os.path.join(data_dir, picture_path))
                    beer.picture = picture
                    beer.save()
                else:
                    pass

            sessions = self.build_random_sessions(models.User.objects.all(),
                NUM_SESSIONS, demo_data['shouts'])

            minutes = sessions[-1][0]
            minutes += MINUTES_IN_DAY
            minutes -= (minutes % (MINUTES_IN_DAY))

            session_start = timezone.make_aware(START_DATE, timezone.utc)
            session_start -= datetime.timedelta(hours=session_start.hour + 4,
                minutes=session_start.minute,
                seconds=session_start.second)
            session_start -= datetime.timedelta(minutes=minutes)

            for minute, drinker, volume_ml, shout in sessions:
                date = session_start + datetime.timedelta(minutes=minute)

                picture_path = None
                if random.random() >= 0.25:
                    d = demo_data['pictures'].get(drinker.username)
                    if d:
                        picture_path = d[0]
                        d.rotate()
                self.do_pour(drinker, date, volume_ml, shout, picture_path)

            print 'Demo data loaded.'

    def do_pour(self, user, when, volume_ml, shout, picture_path):
        be = get_kegbot_backend()
        tap = random_item(models.KegTap)

        # End keg if it's near empty.
        if tap.current_keg and tap.current_keg.remaining_volume_ml() < volume_ml:
            be.end_keg(tap.current_keg, when=when)

        # Start keg if the tap is idle.
        if not tap.current_keg:
            beverage = random_item(models.Beverage)
            be.start_keg(tap, beverage=beverage, when=when)

        drink = be.record_drink(tap, ticks=0, volume_ml=volume_ml,
            username=user.username, pour_time=when, shout=shout)

        if picture_path:
            p = self.create_picture(picture_path, user=user,
                session=drink.session, keg=drink.keg)
            drink.picture = p
            drink.save()
        return drink

    def create_picture(self, path, **kwargs):
        f = File(open(path, 'r'))
        p = models.Picture(image=f, **kwargs)
        name = '%s%s' % (uuid1(), os.path.splitext(f.name)[1])
        p.image.save(name, f)
        p.save()
        return p

    def build_random_sessions(self, all_drinkers, count, shouts):
        MIN_POUR_ML = 200
        MAX_POUR_ML = 500

        pours = []
        minute = 0

        for session_number in xrange(count):
            session = []
            num_drinkers = random.randint(1, len(all_drinkers))
            drinkers = random.sample(all_drinkers, num_drinkers)

            # Drink a little more when others are joining
            session_max = 1000 + min(2000, 500 * len(drinkers))

            for drinker in drinkers:
                subminute = random.randint(0, 15)
                session_total = random.randint(500, session_max)
                session_remain = session_total

                while session_remain > 0:
                    if session_remain <= MIN_POUR_ML:
                        drink_volume = session_remain
                    else:
                        drink_volume = random.randint(MIN_POUR_ML, MAX_POUR_ML)

                    shout = ''
                    if shouts and random.random() >= 0.60:
                        shout = shouts[0]
                        shouts.rotate(1)

                    session.append((minute + subminute, drinker, drink_volume, shout))
                    subminute += int(drink_volume / 40) + random.randint(0, 30)  # 40 mL/minute min plus noise
                    session_remain -= drink_volume

            # session.sort()
            # for minute, drinker, volume, shout in session:
            #     print '%08s %016s %04s  %s' % (minute, drinker, volume, shout)
            # print ''

            pours += session

            # wait 1 - 3 days for next session
            minute += random.randint(1, 3) * MINUTES_IN_DAY
            minute -= (minute % (MINUTES_IN_DAY))
            minute += random.randint(-120, 120)

        pours.sort()
        return pours

    def load_demo_data(self, path):
        demo_data_file = os.path.join(path, 'demo_data.json')
        if not os.path.isfile(demo_data_file):
            raise CommandError('Missing demo_data file: %s' % demo_data_file)

        demo_data = json.loads(open(demo_data_file).read())
        demo_data['pictures'] = {}

        for drinker_name in demo_data['drinkers']:
            demo_data['pictures'][drinker_name] = deque()

            pictures_dir = os.path.join(path, 'pictures', 'drinkers', drinker_name)
            if not os.path.isdir(pictures_dir):
                continue

            for picture_name in (p for p in os.listdir(pictures_dir) if not p.startswith('.')):
                if picture_name == 'mugshot.png':
                    continue
                picture_path = os.path.join(pictures_dir, picture_name)
                demo_data['pictures'][drinker_name].append(picture_path)
            random.shuffle(demo_data['pictures'][drinker_name])

        demo_data['shouts'] = deque(demo_data['shouts'])
        random.shuffle(demo_data['shouts'])

        return demo_data

Command = LoadDemoDataCommand
