<reponame>gitter-badger/text-fabric
from ..core.helpers import console, wrapMessages
from .searchexe import SearchExe
from ..parameters import YARN_RATIO, TRY_LIMIT_FROM, TRY_LIMIT_TO

# Search and SearchExe
#
# HIGH LEVEL description of the roles of Search and SerchExe
#
# SearchExe is the class that runs queries.
# SearchFactory is a class that creates SearchExe instances.
#
# The user is given the factory class as search api S.
#
# The (public) methods of the factory class has methods for searching.
# These methods work on a dedicated instance of SearchExe
# within the factory class.
# The factory class will create this instance when needed.
#
# A reference to the factory class Search is stored in the SearchExe objects.
# This gives the SearchExe objects the possibility to create other
# SearchExe objects to perform auxiliary queries.
# This is needed to run the queries implied by
# quantifiers in the search template.
#
# The search processes done by distinct SearchExe objects do not interfere.
#
# Summarizing: the factory class Search looks to the user like the execution
# class # SearchExe.
# But under the hood, different queries are always performed on different
# instances of SearchExe.

# LOW LEVEL description of the roles of Search and SerchExe
#
# In order to search, you have to create an instance of the SearchExe class.
# This instance contains all parameters and settings relevant to the search.
#
# The factory Search may contain one instance of a SearchExe.
#
# The factory class Search has methods study() and search(),
# which create a SearchExe instance and store it inside the Search.
# After that they call methods with the same name on that SearchExe object.
#
# The factory class Search has methods fetch(), count(), showPlan().
# These call mehtods with the same name on the SearchExe instance stored in
# the factory class Search, if it has been created earlier
# by search() or study().
# If not, an error message is displayed.
#
# All the work involved in searching is performed by the SearchExe object.
# The data of a SearchExe object are tied to a particular search:
# searchTemplate, sets, shallow, silent.
# This also holds for all data that is created during executions of a query:
# qnodes, qedges, results, etc.


class Search(object):

  def __init__(self, api, silent):
    self.api = api
    self.silent = silent
    self.exe = None
    self.perfDefaults = dict(
        yarnRatio=YARN_RATIO,
        tryLimitFrom=TRY_LIMIT_FROM,
        tryLimitTo=TRY_LIMIT_TO,
    )
    self.perfParams = {}
    self.perfParams.update(self.perfDefaults)
    SearchExe.setPerfParams(self.perfParams)

  def tweakPerformance(self, silent=False, **kwargs):
    api = self.api
    error = api.error
    info = api.info
    isSilent = api.isSilent
    setSilent = api.setSilent
    defaults = self.perfDefaults

    wasSilent = isSilent()
    setSilent(silent)
    for (k, v) in kwargs.items():
      if k not in defaults:
        error(f'No such performance parameter: "{k}"', tm=False)
        continue
      if v is None:
        v = defaults[k]
      elif type(v) is not int and k != 'yarnRatio':
        error(f'Performance parameter "{k}" must be set to an integer, not to "{v}"', tm=False)
        continue
      self.perfParams[k] = v
    info('Performance parameters, current values:', tm=False)
    for (k, v) in sorted(self.perfParams.items()):
      info(f'\t{k:<20} = {v:>7}', tm=False)
    SearchExe.setPerfParams(self.perfParams)
    setSilent(wasSilent)

  def search(
      self,
      searchTemplate,
      limit=None,
      sets=None,
      shallow=False,
      silent=True,
      here=True,
      msgCache=False,
  ):
    exe = SearchExe(
        self.api,
        searchTemplate,
        outerTemplate=searchTemplate,
        quKind=None,
        offset=0,
        sets=sets,
        shallow=shallow,
        silent=silent,
        msgCache=msgCache,
        setInfo={},
    )
    if here:
      self.exe = exe
    queryResults = exe.search(limit=limit)
    if type(msgCache) is list:
      messages = wrapMessages(msgCache)
      return (queryResults, messages) if here else (queryResults, messages, exe)
    return queryResults

  def study(
      self,
      searchTemplate,
      strategy=None,
      sets=None,
      shallow=False,
      here=True,
  ):
    exe = SearchExe(
        self.api,
        searchTemplate,
        outerTemplate=searchTemplate,
        quKind=None,
        offset=0,
        sets=sets,
        shallow=shallow,
        silent=False,
        showQuantifiers=True,
        setInfo={},
    )
    if here:
      self.exe = exe
    return exe.study(strategy=strategy)

  def fetch(self, limit=None, msgCache=False):
    exe = self.exe
    if exe is None:
      error = self.api.error
      error('Cannot fetch if there is no previous "study()"')
    else:
      queryResults = exe.fetch(limit=limit)
      if type(msgCache) is list:
        messages = self.api.cache(_asString=True)
        return (queryResults, messages)
      return queryResults

  def count(self, progress=None, limit=None):
    exe = self.exe
    if exe is None:
      error = self.api.error
      error('Cannot count if there is no previous "study()"')
    else:
      exe.count(progress=progress, limit=limit)

  def showPlan(self, details=False):
    exe = self.exe
    if exe is None:
      error = self.api.error
      error('Cannot show plan if there is no previous "study()"')
    else:
      exe.showPlan(details=details)

  def relationsLegend(self):
    exe = self.exe
    if exe is None:
      exe = SearchExe(self.api, '')
    console(exe.relationLegend)

  def glean(self, r):
    T = self.api.T
    F = self.api.F
    E = self.api.E
    fOtype = F.otype.v
    slotType = F.otype.slotType
    maxSlot = F.otype.maxSlot
    eoslots = E.oslots.data

    lR = len(r)
    if lR == 0:
      return ''
    fields = []
    for (i, n) in enumerate(r):
      otype = fOtype(n)
      words = [n] if otype == slotType else eoslots[n - maxSlot - 1]
      if otype == T.sectionTypes[2]:
        field = '{} {}:{}'.format(*T.sectionFromNode(n))
      elif otype == slotType:
        field = T.text(words)
      elif otype in T.sectionTypes[0:2]:
        field = ''
      else:
        field = '{}[{}{}]'.format(
            otype,
            T.text(words[0:5]),
            '...' if len(words) > 5 else '',
        )
      fields.append(field)
    return ' '.join(fields)
