# -*- coding:utf-8 -*-


import os
import sys, collections
from empty import Empty
from flask import Flask, render_template, g, session, redirect, url_for, request, flash, abort
import flask_ld as ld
from flask_ld.utils import lru
from flask_restful import Resource
from nanopub import NanopublicationManager, Nanopublication

from flask_admin import Admin, BaseView, expose

import rdflib
from flask_security import Security, \
    UserMixin, RoleMixin, login_required
from flask_security.core import current_user
from flask_login import AnonymousUserMixin, login_user
from flask_security.forms import RegisterForm
from flask_security.utils import encrypt_password
from werkzeug.datastructures import ImmutableList
from flask_wtf import Form, RecaptchaField
from wtforms import TextField, TextAreaField, StringField, validators
import rdfalchemy
from rdfalchemy.orm import mapper
import sadi
import json
import sadi.mimeparse

from flask_mail import Mail, Message

import database

from datetime import datetime

import markdown

import rdflib.plugin
from rdflib.store import Store
from rdflib.parser import Parser
from rdflib.serializer import Serializer
from rdflib.query import ResultParser, ResultSerializer, Processor, Result, UpdateProcessor
from rdflib.exceptions import Error
rdflib.plugin.register('sparql', Result,
        'rdflib.plugins.sparql.processor', 'SPARQLResult')
rdflib.plugin.register('sparql', Processor,
        'rdflib.plugins.sparql.processor', 'SPARQLProcessor')
rdflib.plugin.register('sparql', UpdateProcessor,
        'rdflib.plugins.sparql.processor', 'SPARQLUpdateProcessor')

# apps is a special folder where you can place your blueprints
PROJECT_PATH = os.path.abspath(os.path.dirname(__file__))
sys.path.insert(0, os.path.join(PROJECT_PATH, "apps"))

basestring = getattr(__builtins__, 'basestring', str)

# we create some comparison keys:
# increase probability that the rule will be near or at the top
top_compare_key = False, -100, [(-2, 0)]
# increase probability that the rule will be near or at the bottom
bottom_compare_key = True, 100, [(2, 0)]

class NamespaceContainer:
    @property
    def prefixes(self):
        result = {}
        for key, value in self.__dict__.items():
            if isinstance(value, Namespace):
                result[key] = value
        return result

from rdfalchemy import *
from flask_ld.datastore import *

# Setup Flask-Security
class ExtendedRegisterForm(RegisterForm):
    identifier = TextField('Identifier', [validators.Required()])
    givenName = TextField('Given Name', [validators.Required()])
    familyName = TextField('Family Name', [validators.Required()])

# Form for full-text search
class SearchForm(Form):
    search_query = StringField('search_query', [validators.DataRequired()])

def to_json(result):
    return json.dumps([dict([(key, value.value if isinstance(value, Literal) else value) for key, value in x.items()]) for x in result.bindings])


class App(Empty):

    def configure_database(self):
        """
        Database configuration should be set here
        """
        self.NS = NamespaceContainer()
        self.NS.RDFS = rdflib.RDFS
        self.NS.RDF = rdflib.RDF
        self.NS.rdfs = rdflib.Namespace(rdflib.RDFS)
        self.NS.rdf = rdflib.Namespace(rdflib.RDF)
        self.NS.owl = rdflib.OWL
        self.NS.xsd   = rdflib.Namespace("http://www.w3.org/2001/XMLSchema#")
        self.NS.dc    = rdflib.Namespace("http://purl.org/dc/terms/")
        self.NS.dcelements    = rdflib.Namespace("http://purl.org/dc/elements/1.1/")
        self.NS.auth  = rdflib.Namespace("http://vocab.rpi.edu/auth/")
        self.NS.foaf  = rdflib.Namespace("http://xmlns.com/foaf/0.1/")
        self.NS.prov  = rdflib.Namespace("http://www.w3.org/ns/prov#")
        self.NS.skos = rdflib.Namespace("http://www.w3.org/2004/02/skos/core#")
        self.NS.cmo = rdflib.Namespace("http://purl.org/twc/ontologies/cmo.owl#")
        self.NS.sio = rdflib.Namespace("http://semanticscience.org/resource/")
        self.NS.sioc_types = rdflib.Namespace("http://rdfs.org/sioc/types#")
        self.NS.sioc = rdflib.Namespace("http://rdfs.org/sioc/ns#")
        self.NS.np = rdflib.Namespace("http://www.nanopub.org/nschema#")
        self.NS.graphene = rdflib.Namespace("http://vocab.rpi.edu/graphene/")
        self.NS.local = rdflib.Namespace(self.config['lod_prefix']+'/')
        self.NS.ov = rdflib.Namespace("http://open.vocab.org/terms/")


        self.db = database.engine_from_config(self.config, "db_")
        load_namespaces(self.db,locals())
        Resource.db = self.db

        self.vocab = Graph()
        #print URIRef(self.config['vocab_file'])
        self.vocab.load(open(self.config['vocab_file']), format="turtle")

        self.role_api = ld.LocalResource(self.NS.prov.Role,"role", self.db.store, self.vocab, self.config['lod_prefix'], RoleMixin)
        self.Role = self.role_api.alchemy

        self.user_api = ld.LocalResource(self.NS.prov.Agent,"user", self.db.store, self.vocab, self.config['lod_prefix'], UserMixin)
        self.User = self.user_api.alchemy

        self.nanopub_api = ld.LocalResource(self.NS.np.Nanopublication,"pub", self.db.store, self.vocab, self.config['lod_prefix'], name="Graph")
        self.Nanopub = self.nanopub_api.alchemy

        self.classes = mapper(self.Role, self.User)
        self.datastore = RDFAlchemyUserDatastore(self.db, self.classes, self.User, self.Role)
        self.security = Security(self, self.datastore,
                                 register_form=ExtendedRegisterForm)
        #self.mail = Mail(self)

    def weighted_route(self, *args, **kwargs):
        def decorator(view_func):
            compare_key = kwargs.pop('compare_key', None)
            # register view_func with route
            self.route(*args, **kwargs)(view_func)

            if compare_key is not None:
                rule = self.url_map._rules[-1]
                rule.match_compare_key = lambda: compare_key

            return view_func
        return decorator

    def configure_views(self):

        def sort_by(resources, property):
            return sorted(resources, key=lambda x: x.value(property))

        class InvitedAnonymousUser(AnonymousUserMixin):
            '''A user that has been referred via kikm references but does not have a user account.'''
            def __init__(self):
                self.roles = ImmutableList()

            def has_role(self, *args):
                """Returns `False`"""
                return False

            def is_active(self):
                return True

            @property
            def is_authenticated(self):
                return True

        def get_label(resource):
            print resource.identifier
            label = resource.graph.preferredLabel(resource.identifier, default = None,
                                labelProperties=(self.NS.RDFS.label, self.NS.skos.prefLabel, self.NS.dc.title, self.NS.foaf.name))
            if len(label) == 0:
                try:
                    label = resource.graph.qname(resource.identifier).split(":")[1].replace("_"," ").title()
                except:
                    label = str(resource.identifier)
            else:
                label = label[0]
            print label
            return label

        @self.before_request
        def load_forms():
            #g.search_form = SearchForm()
            g.ns = self.NS
            g.get_summary = get_summary
            g.get_label = get_label
            g.get_entity = get_entity
            g.rdflib = rdflib
            g.isinstance = isinstance

        @self.login_manager.user_loader
        def load_user(user_id):
            if user_id != None:
                return self.datastore.find_user(id=user_id)
            else:
                return None

        extensions = {
            "rdf": "application/rdf+xml",
            "json": "application/ld+json",
            "ttl": "text/turtle",
            "trig": "application/trig",
            "turtle": "text/turtle",
            "owl": "application/rdf+xml",
            "nq": "application/n-quads",
            "nt": "application/n-triples",
            "html": "text/html"
        }

        dataFormats = {
            "application/rdf+xml" : "xml",
            "application/ld+json" : 'json-ld',
            "text/turtle" : "turtle",
            "application/trig" : "trig",
            "application/n-quads" : "nquads",
            "application/n-triples" : "nt",
            None: "json-ld"
        }

        def get_graphs(graphs):
            query = 'select ?s ?p ?o ?g where {graph ?g {?s ?p ?o} } values ?g { %s }'
            query = query % ' '.join([graph.n3() for graph in graphs])
            print query
            quads = self.db.store.query(query)
            result = Dataset()
            result.addN(quads)
            return result

        def get_entity(entity):
            nanopubs = self.db.query('''select distinct ?s ?p ?o ?g where {
            ?np np:hasAssertion?|np:hasProvenance?|np:hasPublicationInfo? ?g;
                np:hasPublicationInfo ?pubinfo;
                np:hasAssertion ?assertion;
                sio:isAbout ?e.
            graph ?pubinfo { ?assertion dc:created [].}
            graph ?g {?s ?p ?o.}
        }''',initBindings={'e':entity}, initNs={'np':self.NS.np, 'sio':self.NS.sio, 'dc':self.NS.dc})
            result = ConjunctiveGraph()
            result.addN(nanopubs)
            #print result.serialize(format="json-ld")
            return result.resource(entity)

        self.get_entity = get_entity

        def get_summary(resource):
            summary_properties = [
                self.NS.skos.definition,
                self.NS.dc.abstract,
                self.NS.dc.description,
                self.NS.RDFS.comment,
                self.NS.dcelements.description
            ]
            return resource.graph.preferredLabel(resource.identifier, default=[], labelProperties=summary_properties)

        self.get_summary = get_summary

        @self.route('/about.<format>')
        @self.route('/about')
        @self.weighted_route('/<path:name>.<format>', compare_key=bottom_compare_key)
        @self.weighted_route('/<path:name>', compare_key=bottom_compare_key)
        @self.route('/')
        @login_required
        def view(name=None, format=None, view=None):
            if format is not None:
                if format in extensions:
                    content_type = extensions[format]
                else:
                    name = '.'.join([name, format])

            #print name
            if name is not None:
                entity = self.NS.local[name]
            elif 'uri' in request.args:
                entity = URIRef(request.args['uri'])
            else:
                entity = self.NS.local.Home
            content_type = request.headers['Accept'] if 'Accept' in request.headers else '*/*'

            resource = get_entity(entity)
            print resource.identifier, content_type

            htmls = set(['application/xhtml','text/html'])
            if sadi.mimeparse.best_match(htmls, content_type) in htmls:
                return render_view(resource)
            else:
                fmt = dataFormats[sadi.mimeparse.best_match([mt for mt in dataFormats.keys() if mt is not None],content_type)]
                return resource.graph.serialize(format=fmt)

                @self.route('/curate',methods=['GET','POST'])

        #=========================#
        #Curation team Spring 2017#
        @login_required
        def curate():
            database.init_db()
            if not database.does_exist("dblp"):
                database.update_date("dblp","1900-01-01 00:00")
            needs_update = database.check_update_dblp()
            if request.method == "GET":
                return render_template("curate.html",needs_update = needs_update)
            elif request.method == "POST":
                button = request.form['button']
                if button == "Update DBLP":
                    database.update_dblp()
                    needs_update = database.check_update_dblp()
                    return render_template("curate.html",needs_update = needs_update)
        #End of curation implement#
        #=========================#

        views = {}
        def render_view(resource):
            template_args = dict(ns=self.NS,
                                 this=resource, g=g,
                                 current_user=current_user,
                                 isinstance=isinstance,
                                 get_entity=get_entity,
                                 get_summary=get_summary,
                                 rdflib=rdflib,
                                 hasattr=hasattr,
                                 set=set)
            view = None
            if 'view' in request.args:
                view = request.args['view']
            # 'view' is the default view
            content = resource.value(self.NS.sioc.content)
            if (view == 'view' or view is None) and content is not None:
                if content is not None:
                    return render_template('content_view.html',content=content, **template_args)
            value = resource.value(self.NS.prov.value)
            if value is not None and view is None:
                headers = {}
                headers['ContentType'] = 'text/plain'
                content_type = resource.value(self.NS.ov.hasContentType)
                if content_type is not None:
                    headers['ContentType'] = content_type
                if value.datatype == XSD.base64Binary:
                    return base64.b64decode(value.value), 200, headers
                if value.datatype == XSD.hexBinary:
                    return binascii.unhexlify(value.value), 200, headers
                return value.value, 200, headers

            if view is None:
                view = 'view'

            if 'as' in request.args:
                types = [URIRef(request.args['as']), 0]
            else:
                types = list([(x.identifier, 0) for x in resource[RDF.type]])
            #if len(types) == 0:
            types.append([self.NS.RDFS.Resource, 100])
            print types
            type_string = ' '.join(["(%s %d)" % (x.n3(), i) for x, i in types])
            view_query = '''select ?id ?view (count(?mid)+?priority as ?rank) ?class ?c where {
    values (?c ?priority) { %s }
    ?c rdfs:subClassOf* ?mid.
    ?mid rdfs:subClassOf* ?class.
    ?class ?viewProperty ?view.
    ?viewProperty rdfs:subPropertyOf* graphene:hasView.
    ?viewProperty dc:identifier ?id.
} group by ?c ?class order by ?rank
''' % type_string

            print view_query
            views = list(self.vocab.query(view_query, initNs=dict(graphene=self.NS.graphene, dc=self.NS.dc),
                                          initBindings=dict(id=Literal(view))))