'''

Copyright (C) 2017 The Board of Trustees of the Leland Stanford Junior
University.
Copyright (C) 2017 Vanessa Sochat.

This program is free software: you can redistribute it and/or modify it
under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or (at your
option) any later version.

This program 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 Affero General Public
License for more details.

You should have received a copy of the GNU Affero General Public License
along with this program.  If not, see <https://www.gnu.org/licenses/>.

'''

from sqlalchemy import create_engine
from sqlalchemy.orm import (
    scoped_session, 
    sessionmaker
)

from sqlalchemy.ext.declarative import declarative_base
from expfactory.logger import bot
from expfactory.utils import write_json
from expfactory.defaults import (
    EXPFACTORY_SUBID, 
    EXPFACTORY_DATA
)
from glob import glob
import os
import uuid
import pickle
import json
import sys


# RELATIONAL ###################################################################
#
# This is an Expfactory Flask Server database plugin. It implements common 
# functions (generate_subid, save_data, init_db) that should prepare a 
# database and perform actions to save data to it. The functions are added
# to the main application upon initialization of the server. This relational
# module has support for sqlite3, mysql, and postgres
#
################################################################################

def generate_subid(self, token=None, return_user=False):
    '''generate a new user in the database, still session based so we
       create a new identifier.
    '''    
    from expfactory.database.models import Participant
    if not token:
        p = Participant()
    else:
        p = Participant(token=token)
    self.session.add(p)
    self.session.commit()
    if return_user is True:
        return p
    return p.id


def print_user(self, user):
    '''print a relational database user
    ''' 
    status = "active"
    token = user.token

    if token in ['finished', 'revoked']:
        status = token

    if token is None:
        token = ''
  
    subid = "%s\t%s[%s]" %(user.id, token, status)
    print(subid)
    return subid


def list_users(self, user=None):
    '''list users, each having a model in the database. A headless experiment
       will use protected tokens, and interactive will be based on auto-
       incremented ids.
    ''' 
    from expfactory.database.models import Participant
    participants = Participant.query.all()
    users = []
    for user in participants:
        users.append(self.print_user(user))
    return users


# Actions ######################################################################

def generate_user(self):
    '''generate a new user in the database, still session based so we
       create a new identifier. This function is called from the users new 
       entrypoint, and it assumes we want a user generated with a token.
    '''
    token = str(uuid.uuid4())
    return self.generate_subid(token=token, return_user=True)


def finish_user(self, subid):
    '''finish user will remove a user's token, making the user entry not
       accesible if running in headless model'''        

    p = self.revoke_token(subid)
    p.token = "finished"
    self.session.commit()
    return p


def restart_user(self, subid):
    '''restart a user, which means revoking and issuing a new token.'''
    p = self.revoke_token(subid)
    p = self.refresh_token(subid)
    return p


# Tokens #######################################################################


def validate_token(self, token):
    '''retrieve a subject based on a token. Valid means we return a participant
       invalid means we return None
    '''
    from expfactory.database.models import Participant
    p = Participant.query.filter(Participant.token == token).first()
    if p is not None:
        if p.token.endswith(('finished','revoked')):
            p = None
        else:
            p = p.id
    return p


def revoke_token(self, subid):
    '''revoke a token by removing it. Is done at finish, and also available
    as a command line option'''
    from expfactory.database.models import Participant
    p = Participant.query.filter(Participant.id == subid).first()
    if p is not None:
        p.token = 'revoked'
    self.session.commit()
    return p


def refresh_token(self, subid):
    '''refresh or generate a new token for a user'''
    from expfactory.database.models import Participant
    p = Participant.query.filter(Participant.id == subid).first()
    if p is not None:
        p.token = str(uuid.uuid4())
    self.session.commit()
    return p


def save_data(self,session, exp_id, content):
    '''save data will obtain the current subid from the session, and save it
       depending on the database type. Currently we just support flat files'''
    from expfactory.database.models import (
        Participant,
        Result
    )
    subid = session.get('subid')
    token = session.get('token') 

    self.logger.info('Saving data for subid %s' % subid)    

    # We only attempt save if there is a subject id, set at start
    if subid is not None:
        p = Participant.query.filter(Participant.id == subid).first() # better query here

        # Does 
        if self.headless and p.token != token:
            self.logger.warning('%s attempting to use mismatched token [%s] skipping save' %(p.id, token))
        elif self.headless and p.token.endswith(('finished','revoked')):
            self.logger.warning('%s attempting to use expired token [%s] skipping save' %(p.id, token))
        else:

            # Preference is to save data under 'data', otherwise do all of it
            if "data" in content:
                content = content['data']

            result = Result(data=content,
                            exp_id=exp_id,
                            participant_id=p.id) # check if changes from str/int

            # Create and save the result
            self.session.add(result)
            p.results.append(result)
            self.session.commit()

            self.logger.info("Save [participant] %s [result] %s" %(p, result))


Base = declarative_base()
    

def init_db(self):
    '''initialize the database, with the default database path or custom with
       a format corresponding to the database type:

       Examples:

       sqlite:////scif/data/expfactory.db
    '''

    # The user can provide a custom string
    if self.database is None:
        self.logger.error("You must provide a database url, exiting.")
        sys.exit(1)

    self.engine = create_engine(self.database, convert_unicode=True)
    self.session = scoped_session(sessionmaker(autocommit=False,
                                               autoflush=False,
                                               bind=self.engine))

    # Database Setup
    Base.query = self.session.query_property()

    # import all modules here that might define models so that
    # they will be registered properly on the metadata.  Otherwise
    # you will have to import them first before calling init_db()
    import expfactory.database.models
    self.Base = Base
    self.Base.metadata.create_all(bind=self.engine)
