from gftools import fonts_public_pb2
import enum
import re

class Udhr():

  def __init__(self, key, iso639_3, iso15924, bcp47, direction, ohchr, stage, loc, name):
    self.key = key
    self.iso639_3 = iso639_3
    self.iso15924 = iso15924
    self.bcp47 = bcp47
    self.direction = direction
    self.ohchr = ohchr
    self.stage = stage
    self.loc = loc
    self.name = name

    self.title = None
    self.preamble = None
    self.articles = []

  def Parse(self, translation_data):
    if translation_data is None or self.stage < 2:
      return

    if translation_data.find('./{*}title') is not None:
      self.title = translation_data.find('./{*}title').text

    preamble_data = translation_data.find('./{*}preamble')
    if preamble_data is not None:
      if preamble_data.find('./{*}title') is not None:
        self.preamble = {
            'title':
                preamble_data.find('./{*}title').text,
            'content': [
                para.text for para in preamble_data.findall('./{*}para')
                    ],
        }

    articles_data = translation_data.findall('./{*}article')
    for article_data in articles_data:
      title_data = article_data.find('./{*}title')
      article = {
          'id':
              int(article_data.get('number')),
          'title': None if title_data is None else title_data.text,
          'content': [
              para.text for para in article_data.findall('./{*}para')
                  ],
      }
      self.articles.append(article)

  def LoadArticleOne(self, article_one):
    self.articles.append({'id': 0, 'title': None, 'content': [article_one]})

  def GetSampleTexts(self):
    extractor = SampleTextExtractor(self)
    return extractor.GetSampleTexts()


class SampleTextExtractor():

  class TextType(enum.Enum):
    GLYPHS = 1
    WORD = 2
    PHRASE = 3
    SENTENCE = 4
    PARAGRAPH = 5
    PASSAGE = 6

  def __init__(self, udhr):
    self._udhr = udhr
    self._glyphs = iter(self._GetGlyphs())
    self._words = iter(self._GetWords())
    self._paragraphs = iter(self._GetParagraphs())
    self._phrase_history = set()

    self._non_word_regex = re.compile(r'[^\w]+')
    self._space_regex = re.compile(r'\s+')
    self._non_space_regex = re.compile(r'[^\s]+')
    self._non_word_space_regex = re.compile(r'[^\w\s]+')
    self._any_regex = re.compile(r'.')

  def _DisplayLength(self, s):
    """Returns length of given string. Omits combining characters.

    Some entire scripts will not be counted; in those cases, the raw length of
    the string is returned.
    """
    word_space_length = len(self._non_word_space_regex.sub('', s))
    space_length = len(self._non_space_regex.sub('', s))
    if word_space_length == space_length:
      return len(s)
    return word_space_length

  def _GetGlyphs(self):
    seen = set()
    for article in self._udhr.articles:
      for para in article['content']:
        for ch in (self._non_word_regex.sub('', para) or self._space_regex.sub('', para)):
          ch = ch.lower()
          if ch not in seen:
            seen.add(ch)
            yield ch

  def _GetWords(self):
    if self._space_regex.search(self._udhr.articles[0]['content'][0]) is not None:
      splitter = self._space_regex
    else:
      splitter = self._non_word_regex

    seen = set()
    for article in self._udhr.articles:
      for para in article['content']:
        for s in splitter.split(para):
          if s not in seen:
            seen.add(s)
            yield s

  def _GetParagraphs(self):
    if self._udhr.preamble is not None:
      for para in self._udhr.preamble['content']:
        yield para
    for article in self._udhr.articles:
      for para in article['content']:
        yield para

  def _ExtractGlyphs(self, min_chars, max_chars):
    s = ''
    for ch in self._glyphs:
      s += ch.upper()
      if len(s) >= min_chars:
        break
      if ch != ch.upper():
        s += ch
        if len(s) >= min_chars:
          break
    return s

  def _ExtractWord(self, min_chars, max_chars):
    for iterator in [self._words, self._GetWords()]:
      for w in iterator:
        if w is None:
          continue
        if min_chars <= self._DisplayLength(w) <= max_chars:
          return w
    # Fallback to using multiple words for languages with very small words
    return self._ExtractPhrase(min_chars, max_chars)

  def _ExtractPhrase(self, min_chars, max_chars):
    for iterator in [self._paragraphs, self._GetParagraphs()]:
      for para in iterator:
        if para is None:
          continue
        for regex in [self._any_regex, self._space_regex, self._non_word_regex]:
          breaks = [-1]
          for match in regex.finditer(para, min_chars):
            breaks.append(match.start())
            phrase = para[breaks[0]+1:breaks[len(breaks)-1]]
            p_size = self._DisplayLength(phrase)
            while p_size > max_chars and len(breaks) > 1:
              breaks.pop()
              phrase = para[breaks[0]+1:breaks[len(breaks)-1]]
              p_size = self._DisplayLength(phrase)
            if min_chars <= p_size and phrase not in self._phrase_history:
              self._phrase_history.add(phrase)
              return phrase
    return self._ExtractParagraph(min_chars, max_chars)

  def _ExtractSentence(self, min_chars, max_chars):
    # Sentence delimination may differ between scripts, so tokenizing on spaces
    # would be unreliable. Prefer to use _ExtractPhrase.
    return self._ExtractPhrase(min_chars, max_chars)

  def _ExtractParagraph(self, min_chars, max_chars):
    for iterator in [self._paragraphs, self._GetParagraphs()]:
      for para in iterator:
        if para is None:
          continue
        if min_chars <= self._DisplayLength(para) <= max_chars:
          return para
    # Paragraphs likely insufficient length; try combining into passages
    return self._ExtractPassage(min_chars, max_chars)

  def _ExtractPassage(self, min_chars, max_chars):
    p = []
    p_size = 0
    while p_size < min_chars:
      for iterator in [self._paragraphs, self._GetParagraphs()]:
        for para in iterator:
          if para is None:
            continue
          p.append(para)
          p_size = self._DisplayLength(' '.join(p))
          if max_chars < p_size:
            p.pop()
          elif min_chars <= p_size:
            return '\n'.join(p)
    assert len(p) > 0, 'Unable to extract passage: ' + self._udhr.key
    if len(p) == 0:
      p.append([p for p in self._GetParagraphs()][0])
    return '\n'.join(p)

  def _Get(self, text_type, **kwargs):
    if 'char_count' in kwargs:
      min_chars = kwargs['char_count']
      max_chars = kwargs['char_count']
    else:
      min_chars = kwargs['min_chars']
      max_chars = kwargs['max_chars']
    if text_type == self.TextType.GLYPHS:
      return self._ExtractGlyphs(min_chars, max_chars)
    if text_type == self.TextType.WORD:
      return self._ExtractWord(min_chars, max_chars)
    if text_type == self.TextType.PHRASE:
      return self._ExtractPhrase(min_chars, max_chars)
    if text_type == self.TextType.SENTENCE:
      return self._ExtractSentence(min_chars, max_chars)
    if text_type == self.TextType.PARAGRAPH:
      return self._ExtractParagraph(min_chars, max_chars)
    if text_type == self.TextType.PASSAGE:
      return self._ExtractPassage(min_chars, max_chars)
    raise Exception('Unsupported text type: ' + text_type)

  def GetSampleTexts(self):
    sample_text = fonts_public_pb2.SampleTextProto()
    sample_text.masthead_full = self._Get(self.TextType.GLYPHS, char_count = 4)
    sample_text.masthead_partial = self._Get(self.TextType.GLYPHS, char_count = 2)
    sample_text.styles = self._Get(self.TextType.PHRASE, min_chars = 40, max_chars = 60)
    sample_text.tester = self._Get(self.TextType.PHRASE, min_chars = 60, max_chars = 90)
    sample_text.poster_sm = self._Get(self.TextType.PHRASE, min_chars = 10, max_chars = 17)
    sample_text.poster_md = self._Get(self.TextType.PHRASE, min_chars = 6, max_chars = 12)
    sample_text.poster_lg = self._Get(self.TextType.WORD, min_chars = 3, max_chars = 8)
    sample_text.specimen_48 = self._Get(self.TextType.SENTENCE, min_chars = 50, max_chars = 80)
    sample_text.specimen_36 = self._Get(self.TextType.PARAGRAPH, min_chars = 100, max_chars = 120)
    sample_text.specimen_32 = self._Get(self.TextType.PARAGRAPH, min_chars = 140, max_chars = 180)
    sample_text.specimen_21 = self._Get(self.TextType.PASSAGE, min_chars = 300, max_chars = 500)
    sample_text.specimen_16 = self._Get(self.TextType.PASSAGE, min_chars = 550, max_chars = 750)
    return sample_text

