text: #!/usr/bin/python3
  # -*- coding: utf-8 -*-
  
  """
  .. module:: api
      :platform: Unix
      :synopsis: the API of Dragonfire that contains the endpoints.
  
  .. moduleauthor:: Mehmet Mert Yıldıran <mert.yildiran@bil.omu.edu.tr>
  """
  
  from threading import Thread  # Thread-based parallelism
  import json  # JSON encoder and decoder
  import re  # Regular expression operations
  from random import randrange  # Generate pseudo-random numbers
  from datetime import datetime  # Basic date and time types
  
  from ava.config import Config  # Credentials for the database connection
  from ava.arithmetic import arithmetic_parse  # Submodule of Dragonfire to analyze arithmetic expressions
  from ava.database import User, Notification  # Submodule of Dragonfire module that contains the database schema
  
  import hug  # Embrace the APIs of the future
  from hug_middleware_cors import CORSMiddleware  # Middleware for allowing CORS (cross-origin resource sharing) requests from hug servers
  import waitress  # A production-quality pure-Python WSGI server with very acceptable performance
  import wikipedia as wikipedia_lib  # Python library that makes it easy to access and parse data from Wikipedia
  import youtube_dl  # Command-line program to download videos from YouTube.com and other video sites
  import jwt  # JSON Web Token implementation in Python
  from sqlalchemy.orm.exc import NoResultFound  # the Python SQL toolkit and Object Relational Mapper
  
  
  @hug.authentication.token
  def token_authentication(token):
      """Method to compare the given token with precomputed token.
  
      Args:
          token (str):  API token.
  
      Returns:
          bool.  The return code::
  
              True -- The token is correct!
              False -- The token is invalid!
      """
  
      try:
          jwt.decode(token, Config.SUPER_SECRET_KEY, algorithm='HS256')
          return True
      except:
          return False
  
  
  # Natural Language Processing realted API endpoints START
  
  @hug.post('/tag', requires=token_authentication)
  def tagger_end(text):
      """**Endpoint** to return **POS Tagging** result of the given text.
  
      Args:
          text (str):  Text.
  
      Returns:
          JSON document.
      """
  
      return json.dumps(tagger(text), indent=4)
  
  
  def tagger(text):
      """Method to encapsulate **POS Tagging** process.
  
      Args:
          text (str):  Text.
  
      Returns:
          (list) of (dict)s:  List of dictionaries.
      """
  
      data = []
      doc = nlp(text)
      for token in doc:
          parse = {
              'text': token.text,
              'lemma': token.lemma_,
              'pos': token.pos_,
              'tag': token.tag_,
              'dep': token.dep_,
              'shape': token.shape_,
              'is_alpha': token.is_alpha,
              'is_stop': token.is_stop
          }
          data.append(parse)
      return data
  
  
  @hug.post('/dep', requires=token_authentication)
  def dependency_parser_end(text):
      """**Endpoint** to return **Dependency Parse** result of the given text.
  
      Args:
          text (str):  Text.
  
      Returns:
          JSON document.
      """
  
      return json.dumps(dependency_parser(text), indent=4)
  
  
  def dependency_parser(text):
      """Method to encapsulate **Dependency Parse** process.
  
      Args:
          text (str):  Text.
  
      Returns:
          (list) of (dict)s:  List of dictionaries.
      """
  
      data = []
      doc = nlp(text)
      for chunk in doc.noun_chunks:
          parse = {
              'text': chunk.text,
              'root_text': chunk.root.text,
              'root_dep': chunk.root.dep_,
              'root_head_text': chunk.root.head.text,
          }
          data.append(parse)
      return data
  
  
  @hug.post('/ner', requires=token_authentication)
  def entity_recognizer_end(text):
      """**Endpoint** to return **Named Entity Recognition** result of the given text.
  
      Args:
          text (str):  Text.
  
      Returns:
          JSON document.
      """
  
      return json.dumps(entity_recognizer(text), indent=4)
  
  
  def entity_recognizer(text):
      """Method to encapsulate **Named Entity Recognition** process.
  
      Args:
          text (str):  Text.
  
      Returns:
          (list) of (dict)s:  List of dictionaries.
      """
  
      data = []
      doc = nlp(text)
      for ent in doc.ents:
          parse = {
              'text': ent.text,
              'start_char': ent.start_char,
              'end_char': ent.end_char,
              'label': ent.label_,
          }
          data.append(parse)
      return data
  
  
  @hug.post('/token', requires=token_authentication)
  def tokenizer_end(text):
      """**Endpoint** to **tokenize** the given text.
  
      Args:
          text (str):  Text.
  
      Returns:
          JSON document.
      """
  
      return json.dumps(tokenizer(text), indent=4)
  
  
  def tokenizer(text):
      """Method to encapsulate **tokenization** process.
  
      Args:
          text (str):  Text.
  
      Returns:
          (list) of (dict)s:  List of dictionaries.
      """
  
      data = []
      doc = nlp(text)
      for token in doc:
          data.append(token.text)
      return data
  
  
  @hug.post('/sent', requires=token_authentication)
  def sentence_segmenter_end(text):
      """**Endpoint** to return **Sentence Segmentation** result of the given text.
  
      Args:
          text (str):  Text.
  
      Returns:
          JSON document.
      """
  
      return json.dumps(sentence_segmenter(text), indent=4)
  
  
  def sentence_segmenter(text):
      """Method to encapsulate **Sentence Segmentation** process.
  
      Args:
          text (str):  Text.
  
      Returns:
          (list) of (dict)s:  List of dictionaries.
      """
  
      data = []
      doc = nlp(text)
      for sent in doc.sents:
          data.append(sent.text)
      return data
  
  
  # All-in-One NLP
  @hug.post('/cmd', requires=token_authentication)
  def cmd(text):
      """Serves the **all Natural Language Processing features** (parsers) of :mod:`spacy` in a single **endpoint**.
  
      Combines the results of these methods into a single JSON document:
  
       - :func:`ava.api.tagger` method (**POS Tagging**)
       - :func:`ava.api.dependency_parser` method (**Dependency Parse**)
       - :func:`ava.api.entity_recognizer` method (**Named Entity Recognition**)
  
      Args:
          text (str):  Text.
  
      Returns:
          JSON document.
      """
  
      data = []
      sents = sentence_segmenter(text)
      for sent in sents:
          sent_data = {}
          sent_data['tags'] = tagger(sent)
          sent_data['deps'] = dependency_parser(sent)
          sent_data['ners'] = entity_recognizer(sent)
          data.append(sent_data)
      return json.dumps(data, indent=4)
  
  # Natural Language Processing realted API endpoints END
  
  
  # Directly on server-side Q&A related API endpoints START
  
  @hug.post('/math', requires=token_authentication)
  def math(text):
      """**Endpoint** to return the response of :func:`ava.arithmetic.arithmetic_parse` function.
  
      Args:
          text (str):  Text.
  
      Returns:
          JSON document.
      """
  
      response = arithmetic_parse(text)
      if not response:
          response = ""
      return json.dumps(response, indent=4)
  
  
  @hug.post('/learn', requires=token_authentication)
  def learn(text, user_id):
      """**Endpoint** to return the response of :func:`ava.learn.Learner.respond` method.
  
      Args:
          text (str):         Text.
          user_id (int):      User's ID.
  
      Returns:
          JSON document.
      """
  
      response = learner.respond(text, is_server=True, user_id=user_id)
      if not response:
          response = ""
      return json.dumps(response, indent=4)
  
  
  @hug.post('/omni', requires=token_authentication)
  def omni(text, gender_prefix):
      """**Endpoint** to return the answer of :func:`ava.omniscient.Omniscient.respond` method.
  
      Args:
          text (str):             Text.
          gender_prefix (str):    Prefix to address/call user when answering.
  
      Returns:
          JSON document.
      """
  
      answer = omniscient.respond(text, userin=userin, user_prefix=gender_prefix, is_server=True)
      if not answer:
          answer = ""
      return json.dumps(answer, indent=4)
  
  
  @hug.post('/deep', requires=token_authentication)
  def deep(text, gender_prefix):
      """**Endpoint** to return the response of :func:`ava.deepconv.DeepConversation.respond` method.
  
      Args:
          text (str):             Text.
          gender_prefix (str):    Prefix to address/call user when answering.
  
      Returns:
          JSON document.
      """
  
      answer = dc.respond(text, user_prefix=gender_prefix)
      return json.dumps(answer, indent=4)
  
  
  # All-in-One Answering
  @hug.post('/answer', requires=token_authentication)
  def answer(text, gender_prefix, user_id, previous=None):
      """Serves the **all Q&A related API endpoints** in a single **endpoint**.
  
      Combines the results of these methods into a single JSON document:
  
       - :func:`ava.arithmetic.arithmetic_parse` function
       - :func:`ava.learn.Learner.respond` method
       - :func:`ava.omniscient.Omniscient.respond` method
       - :func:`ava.deepconv.DeepConversation.respond` method
  
      Args:
          text (str):             User's current command.
          gender_prefix (str):    Prefix to address/call user when answering.
          user_id (int):          User's ID.
          previous (str):         User's previous command.
  
      Returns:
          JSON document.
      """
  
      data = {}
      text = coref.resolve_api(text, previous)
      subject, subjects, focus, subject_with_objects = omniscient.semantic_extractor(text)
      data['subject'] = subject
      data['focus'] = focus
      answer = arithmetic_parse(text)
      if not answer:
          answer = learner.respond(text, is_server=True, user_id=user_id)
          if not answer:
              answer = omniscient.respond(text, userin=userin, user_prefix=gender_prefix, is_server=True)
              if not answer:
                  answer = dc.respond(text, user_prefix=gender_prefix)
      data['answer'] = answer
      return json.dumps(data, indent=4)
  
  # Directly on server-side Q&A related API endpoints END
  
  
  @hug.post('/wikipedia', requires=token_authentication)
  def wikipedia(query, gender_prefix):
      """**Endpoint** to make a **Wikipedia search** and return its **text content**.
  
      Args:
          query (str):            Search query.
          gender_prefix (str):    Prefix to address/call user when answering.
  
      Returns:
          JSON document.
      """
  
      response = ""
      url = ""
      wikiresult = wikipedia_lib.search(query)
      if len(wikiresult) == 0:
          response = "Sorry, " + gender_prefix + ". But I couldn't find anything about " + query + " in Wikipedia."
      else:
          wikipage = wikipedia_lib.page(wikiresult[0])
          wikicontent = "".join([
              i if ord(i) < 128 else ' '
              for i in wikipage.content
          ])
          wikicontent = re.sub(r'\([^)]*\)', '', wikicontent)
          response = " ".join(sentence_segmenter(wikicontent)[:3])
          url = wikipage.url
      data = {}
      data['response'] = response
      data['url'] = url
      return json.dumps(data, indent=4)
  
  
  @hug.post('/youtube', requires=token_authentication)
  def youtube(query, gender_prefix):
      """**Endpoint** to make a **YouTube search** and return the **video title** and **URL**.
  
      Args:
          query (str):            Search query.
          gender_prefix (str):    Prefix to address/call user when answering.
  
      Returns:
          JSON document.
      """
  
      response = ""
      url = ""
      info = youtube_dl.YoutubeDL({}).extract_info('ytsearch:' + query, download=False, ie_key='YoutubeSearch')
      if len(info['entries']) > 0:
          response = info['entries'][0]['title']
          url = "https://www.youtube.com/watch?v=%s" % (info['entries'][0]['id'])
          response = "".join([
              i if ord(i) < 128 else ' '
              for i in response
          ])
      else:
          response = "No video found, " + gender_prefix + "."
      data = {}
      data['response'] = response
      data['url'] = url
      return json.dumps(data, indent=4)
  
  
  @hug.post('/notification', requires=token_authentication)
  def notification(user_id, location, gender_prefix, response=None):
      """**Endpoint** to serve the **notifications** from the **database**.
  
      Args:
          user_id (int):          User's ID.
          location (str):         *Development in progress...*
          gender_prefix (str):    Prefix to address/call user when answering.
  
      Returns:
          JSON document.
      """
  
      try:
          user = db_session.query(User).filter(User.id == int(user_id)).one()
          if not db_session.query(Notification).count() > 0:
              response.status = hug.HTTP_404
              return
          rand = randrange(0, db_session.query(Notification).count())
  
          notification = db_session.query(Notification).filter(Notification.is_active)[rand]
  
          if notification.capitalize == 1:
              gender_prefix = gender_prefix.capitalize()
  
          data = {}
          data['url'] = notification.url
          data['title'] = notification.title
          data['message'] = notification.message.format(gender_prefix, user.name)
          return json.dumps(data, indent=4)
      except NoResultFound:
          response.status = hug.HTTP_404
          return
  
  
  # Endpoint to handle registration requests
  @hug.post('/register')
  def register(name, gender, birth_date, reg_key, response=None):
      """**Endpoint** to handle **registration requests**.
  
      Args:
          name (str):         User's name.
          gender (str):       User's gender.
          birth_date (str):   User's birth date.
          reg_key (str):      Registration key.
  
      Returns:
          JSON document.
      """
  
      if reg_key != server_reg_key:
          response.status = hug.HTTP_403
          return
  
      new_user = User(name=name, gender=gender, birth_date=datetime.strptime(birth_date, "%Y-%m-%d").date())
      db_session.add(new_user)
      db_session.commit()
  
      data = {}
      data['id'] = new_user.id
      data['token'] = jwt.encode({'id': new_user.id, 'name': name, 'gender': gender, 'birth_date': birth_date}, Config.SUPER_SECRET_KEY, algorithm='HS256').decode('ascii')
      return json.dumps(data, indent=4)
  
  
  class Run():
      """Class to Run the API.
  
      .. note::
  
          Creating an object from this class is automatically starts the API server.
  
      """
  
      def __init__(self, nlp_ref, learner_ref, omniscient_ref, dc_ref, coref_ref, userin_ref, reg_key, port_number, db_session_ref, dont_block=False):
          """Initialization method of :class:`ava.api.Run` class
  
          This method starts an API server using :mod:`waitress` (*a pure-Python WSGI server*)
          on top of lightweight :mod:`hug` API framework.
  
          Args:
              nlp_ref:                :mod:`spacy` model instance.
              learner_ref:            :class:`ava.learn.Learner` instance.
              omniscient_ref:         :class:`ava.omniscient.Omniscient` instance.
              dc_ref:                 :class:`ava.deepconv.DeepConversation` instance.
              userin_ref:             :class:`ava.utilities.TextToAction` instance.
              reg_key (str):          Registration key of the API.
              port_number (int):      Port number that the API will be served.
              db_session_ref:         SQLAlchemy's :class:`DBSession()` instance.
          """
  
          global nlp
          global learner
          global omniscient
          global dc
          global coref
          global userin
          global server_reg_key
          global db_session
          nlp = nlp_ref  # Load en_core_web_sm, English, 50 MB, default model
          learner = learner_ref
          omniscient = omniscient_ref
          dc = dc_ref
          coref = coref_ref
          userin = userin_ref
          server_reg_key = reg_key
          db_session = db_session_ref
          app = hug.API(__name__)
          app.http.output_format = hug.output_format.text
          app.http.add_middleware(CORSMiddleware(app))
          self.waitress_thread = Thread(target=waitress.serve, args=(__hug_wsgi__, ), kwargs={"port": port_number})
          if dont_block:
              self.waitress_thread.daemon = True
          self.waitress_thread.start()
          if not dont_block:
              self.waitress_thread.join()
  
  
  if __name__ == '__main__':
      app = hug.API(__name__)
      app.http.output_format = hug.output_format.text
      app.http.add_middleware(CORSMiddleware(app))
      waitress.serve(__hug_wsgi__, port=8000)
  