# coding: utf8
# 1/5/2015 jichi
#
# Example:
# * A comment
# $macro\tdefinition
# from\tto

if __name__ == '__main__':
  import sys
  sys.path.append('..')

import os, re
from sakurakit.skdebug import dwarn
from sakurakit import skstr

COMMENT_CHARS = '#', '*'
MACRO_CHAR = '$'
SPLIT_CHAR = '\t'

_isregex = skstr.contains_re_chars

def _iterreadfile(path):
  """
  @param  path  unicode
  @yield  unicode
  """
  for line in open(path, 'r'):
    line = line.strip()
    if line:
      yield line

def _iterreadfiles(paths):
  """
  @param  paths  [unicode]
  @yield  unicode
  """
  for path in paths:
    for line in open(path, 'r'):
      line = line.strip()
      if line:
        yield line

def readfile(path):
  """
  @param  path  unicode
  @return  the same as parselines
  """
  try: return parselines(_iterreadfile(path))
  except Exception, e: dwarn(e)

def readfiles(paths):
  """
  @param  paths  [unicode]
  @return  the same as parselines
  """
  try: return parselines(_iterreadfiles(paths))
  except Exception, e: dwarn(e)

_RE_MACRO = re.compile('{{(.+?)}}')
def _applymacros(macros, text):
  """
  @param  text  unicode
  @param  macros  {unicode name:unicode repl}
  @return  unicode
  """
  if text and '{{' in text:
    for m in _RE_MACRO.finditer(text):
      macro = m.group(1)
      repl = macros.get(macro)
      if repl is None:
        dwarn("missing macro:", macro, text)
      else:
        text = text.replace("{{%s}}" % macro, repl)
  return text

def _evalmacros(macros, limit=1000):
  """
  @param  macros  {unicode name:unicode value}
  @param  text  unicode
  @param* limit  int  maximum iteration count
  @return  unicode
  """
  for count in xrange(1, limit):
    dirty = False
    for pattern,text in macros.iteritems(): # not iteritems as I will modify ret
      if text and '{{' in text:
        dirty = True
        ok = False
        for m in _RE_MACRO.finditer(text):
          macro = m.group(1)
          repl = macros.get(macro)
          if repl:
            text = text.replace("{{%s}}" % macro, repl)
            ok = True
          else:
            dwarn("missing macro", macro, text)
            ok = False
            break
        if ok:
          macros[pattern] = text
        else:
          macros[pattern] = None # delete this pattern
    if not dirty:
      break
  if count == limit - 1:
    dwarn("recursive macro definition")

def parselines(lines):
  """
  @return  [(unicode pattern, unicode replacement)]  rules
  """
  macros = {} # [(unicode name, unicode value)]  rules
  rules = []  # [(unicode pattern, unicode replacement)]  rules

  rulekeys = set()

  for line in lines:
    first = line[0]
    if first in COMMENT_CHARS:
      continue
    left, ch, right = line.partition(SPLIT_CHAR)
    right = right.strip()
    ismacro = first == MACRO_CHAR
    if ismacro:
      if not right:
        dwarn("missing definition for macro:", line)
        #continue
      left = left[1:]
    left = left.strip()
    if not left:
      dwarn("missing pattern:", line)
      continue
    #right = _applymacros(macros, right)
    #if not right:
    #  dwarn("missing definition for macro:", line)
    #  #continue

    if ismacro:
      if left in macros:
        dwarn("macro redefinition:", line)
      macros[left] = right
      _evalmacros(macros)
    else:
      left = _applymacros(macros, left)
      if not left:
        dwarn("missing pattern after applying macros:", line)
        continue
      right = _applymacros(macros, right)
      if '{{' in left or '}}' in left:
        dwarn("macro not applied:", line)
      if '{{' in right or '}}' in right:
        dwarn("macro not applied:", line)
      if _verify(left, right):
        if left in rulekeys:
          dwarn("rule redefinition:", line)
        else:
          rulekeys.add(left)
        rules.append((left, right))

  return rules

def _createheader():
  """
  @return  unicode
  """
  from datetime import datetime
  now = datetime.now()
  ts = now.strftime("%Y-%m-%d %H:%M")
  return "* Generated by VNR on %s\n" % ts

def writefile(path, rules):
  """
  @param  path  unicode
  @param  rules
  @return  bool
  """
  fmt = "%s" + SPLIT_CHAR + "%s\n"
  try:
    if os.path.exists(path):
      os.remove(path)
    with open(path, 'w') as f:
      line = _createheader()
      f.write(line)
      for k,v in rules:
        line = fmt % (k,v)
        line = line.encode('utf8')
        f.write(line)
      return True
  except Exception, e:
    dwarn(e)
    return False

def _verify(k, v):
  """
  @param  k  unicode
  @param  v  unicode
  @return  bool
  """
  ok = True
  if _isregex(k):
    try:
      re.compile(k)
      re.compile(v)
    except Exception, e:
      dwarn("invalid regex: %s: %s => %s" % (e, k, v))
      ok = False
  return ok

def sub(rules, text):
  """
  @param  rules  [unicode k, unicode v]
  @param  text  unicode
  @return  unicode
  """
  for k,v in rules:
    if _isregex(k):
      text = re.sub(k, v, text)
    else:
      text = text.replace(k, v)
  return text

if __name__ == '__main__':
  inpath = "test.txt"
  outfile = 'out.txt'
  rules = readfile(inpath)
  writefile(outfile, rules)

  text = "hello"
  text = sub(rules, text)
  print text

# EOF
