import simplejson as json
from datetime import datetime
from flask.ext.sqlalchemy import SQLAlchemy
from sqlalchemy.ext.mutable import Mutable

import board
from app import db, app
from utils import parse_valve_heropedia, complete_hero_data, API_DATA

# Model independant get_or_create
def get_or_create_instance(session, model, **kwargs):
    instance = session.query(model).filter_by(**kwargs).first()
    if instance:
        return instance
    else:
        instance = model(**kwargs)
        session.add(instance)
        return instance

# Get a little of that Mongoness back
class Json(db.TypeDecorator):
    impl = db.Text

    def process_bind_param(self, value, dialect):
        if value is not None:
            value = json.dumps(value)
        return value

    def process_result_value(self, value, dialect):
        try:
            if value is not None:
                value = json.loads(value)
        except ValueError:
            return {}
        return value

# Mongoness factor - phase 2
class MutableDict(Mutable, dict):
    @classmethod
    def coerce(cls, key, value):
        if not isinstance(value, MutableDict):
            if isinstance(value, dict):
                return MutableDict(value)
            return Mutable.coerce(key,value)
        else:
            return value

    def __delitem__(self, key):
        dict.__delitem__(self, key)
        self.changed()

    def __setitem__(self, key, value):
        dict.__setitem__(self, key, value)
        self.changed()

    def __getstate__(self):
        return dict(self)

    def __setstate__(self, state):
        self.update(self)


class User(db.Model):
	id = db.Column(db.Integer, primary_key=True)
	steam_id = db.Column(db.String(40), unique=True)
        forum_id = db.Column(db.Integer)
        teamspeak_id = db.Column(db.String(200), unique=True)

	nickname = db.Column(db.String(80))
	avatar = db.Column(db.String(255))

        bio_text = db.Column(db.String(4096))
        created = db.Column(db.DateTime)
        last_seen = db.Column(db.DateTime)
        twitch = db.Column(db.String(60))
        hitbox = db.Column(db.String(60))

        admin = db.Column(db.Boolean)
        public = db.Column(db.Boolean)
        logo = db.Column(db.Boolean)

        points_from_events = db.Column(db.Integer)
        points_from_ts3 = db.Column(db.Integer)
        points_from_forum = db.Column(db.Integer)

        ts3_starttime = db.Column(db.DateTime)
        ts3_endtime = db.Column(db.DateTime)
        ts3_rewardtime = db.Column(db.DateTime)
        ts3_connections = db.Column(MutableDict.as_mutable(Json))

        last_post_reward = db.Column(db.Integer)
        winrate_data = db.Column(MutableDict.as_mutable(Json))


        @classmethod
        def get_or_create(self, steam_id):
            return get_or_create_instance(db.session, User, steam_id=steam_id)

        @classmethod
        def get_streaming_users(self):
            twitch_streams = []
            hitbox_streams = []
            for user in User.query.all():
		if user.points_from_events + user.points_from_ts3 + user.points_from_forum < 5: continue
                if user.twitch:
                    twitch_streams.append(user.twitch)
                if user.hitbox:
                    hitbox_streams.append(user.hitbox)

            return {'twitch': twitch_streams, 'hitbox': hitbox_streams}

        def __init__(self, steam_id):
            self.steam_id = steam_id
            self.az_completions = 0
            self.ts3_connections = {'list':[]}
            self.ts3_rewardtime = datetime.utcnow()
            self.created = datetime.utcnow()
            self.last_seen = datetime.utcnow()
            self.bio_text = None 
            self.points_from_events = 0
            self.points_from_ts3 = 0
            self.points_from_forum = 0
            self.admin = False
            self.public = True
            self.biglogo = True

        def update_connection(self, reward_threshold=30):
            now = datetime.utcnow()
            self.ts3_starttime = self.ts3_starttime or now
            self.ts3_endtime = now
            # Add general TS3 points here
            if self.ts3_endtime and self.ts3_rewardtime:
                delta = (self.ts3_endtime - self.ts3_rewardtime)
                duration = (delta.seconds % 3600) // 60
                if duration > reward_threshold:
                    self.ts3_rewardtime = datetime.utcnow()
                    self.points_from_ts3 += 1
                else:
                    self.ts3_rewardtime = datetime.utcnow()
            self.last_seen = datetime.utcnow()
            db.session.commit();

        def finalize_connection(self):
            self.ts3_connections['list'].append({'starttime': self.ts3_starttime, 'endtime': self.ts3_endtime})
            self.ts3_startime = None
            self.ts3_endtime = None
            db.session.commit();

        def update_forum_posts(self, reward_threshold=5):
            if self.forum_id:
                posts = board.Users.select().where(board.Users.id == int(self.forum_id))[0].posts
                if self.last_post_reward:
                    num_points = (posts - self.last_post_reward) / reward_threshold
                    if num_points > 0:
                        self.points_from_forum += num_points
                        self.last_post_reward += num_points * reward_threshold
                else:
                    # Initialize if this is the first reward
                    self.last_post_reward = posts
                db.session.commit()

        @property
        def is_active(self):
            return self.ts3_starttime and True or False 

	def __repr__(self):
            return '<User {}>'.format(self.id)

class TeamspeakData(db.Model):
        id = db.Column(db.Integer, primary_key=True)
        time = db.Column(db.DateTime())
        clients = db.Column(Json())

        def __init__(self, clientlist):
            self.time = datetime.utcnow() 
            self.clients = clientlist 

class Event(db.Model):
        id = db.Column(db.Integer, primary_key=True)
        name = db.Column(db.String(200))
        desc = db.Column(db.String(4096))
        type = db.Column(db.String(20))

        start_time = db.Column(db.DateTime)
        end_time = db.Column(db.DateTime)
        points = db.Column(db.Integer)
        reward_threshold = db.Column(db.Integer)

        total_subchans = db.Column(db.Integer)
        channels = db.Column(MutableDict.as_mutable(Json))
        participants = db.Column(MutableDict.as_mutable(Json))

        def __init__(self, id):
            self.channels = {'event_cid':None, 'cids':[]}

        @classmethod
        def get_or_create(self, event_id):
            return get_or_create_instance(db.session, Event, id=event_id)

        @property
        def cids(self):
            return self.channels['cids']

        @property
        def event_cid(self):
            return self.channels['event_cid']

        @property
        def channel_ids(self):
            cids = self.channels['cids']
            if self.channels['event_cid']:
                cids.append(self.channels['event_cid'])
            return cids

        def create_channels(self):
            import ts3
            server = ts3.TS3Server(app.config['TS3_HOST'], app.config['TS3_PORT'])
            server.login(app.config['TS3_USERNAME'], app.config['TS3_PASSWORD'])
            server.use(1)
            # Create the parent channel
            if not self.event_cid:
                # Find the LFG channel and place this one after
                response = server.send_command('channelfind', keys={'pattern': 'Looking for Group'})
                if response.is_successful:
                    cid = response.data[0]['cid']
                response = server.send_command('channelcreate', keys={'channel_name':self.name.encode('utf-8'), 'channel_flag_semi_permanent':'1', 'channel_order':cid})
                if response.is_successful:
                    self.channels['event_cid'] = response.data[0]['cid']
            # Create the subchannels
            if not self.cids:
                cids = []
                keys = {'channel_name':'Event Room #{}'.format(len(self.cids) + 1), 'channel_flag_semi_permanent':'1', 'cpid':self.event_cid.encode('utf-8')}
                response = server.send_command('channelcreate', keys=keys)
                if response.is_successful:
                    parent_cid = response.data[0]['cid']
                    cids.append(parent_cid)
                else:
                    raise UserWarning("channelcreate failed")
                response = server.send_command('channelcreate', keys={'channel_name':'Radiant Team', 'channel_flag_semi_permanent':'1', 'cpid':parent_cid})
                if response.is_successful:
                    cids.append(response.data[0]['cid'])
                response = server.send_command('channelcreate', keys={'channel_name':'Dire Team', 'channel_flag_semi_permanent':'1', 'cpid':parent_cid})
                if response.is_successful:
                    cids.append(response.data[0]['cid'])
                response = server.send_command('channelcreate', keys={'channel_name':'Spectators', 'channel_flag_semi_permanent':'1', 'cpid':parent_cid})
                if response.is_successful:
                    cids.append(response.data[0]['cid'])
                self.channels['cids'] = cids
            db.session.commit()
        
        def remove_channels(self):
            import ts3
            server = ts3.TS3Server(app.config['TS3_HOST'], app.config['TS3_PORT'])
            server.login(app.config['TS3_USERNAME'], app.config['TS3_PASSWORD'])
            server.use(1)
            response = server.send_command('channeldelete', keys={'cid':self.event_cid.encode('utf-8'), 'force':'1'})
            if response.is_successful:
                self.channels = {'event_cid': None, 'cids':[]}
                db.session.commit()

        @property
        def active(self):
            current_time = datetime.utcnow()
            return self.start_time < current_time and current_time < self.end_time

        @property
        def expired(self):
            current_time = datetime.utcnow()
            return self.end_time < curent_time

        def add_participant(self, user):
            entry = self.participants.setdefault(user, {'start_time': datetime.utcnow() })
            entry['end_time'] = datetime.utcnow()
            if 'points' not in entry and (entry['end_time'] - entry['start_time']) > self.reward_threshold:
                user.points_from_events += self.points
                entry['points'] = self.points
            db.session.commit()

        @property
        def participants(self):
            return tuple(self.participants)

        def __repr__(self):
            return '<Event {}>'.format(self.id)
