# keyword_files module
# provides basic functions for searching for keywords in an ascii control file such as a nexus deck
# ascii file must already have been opened for reading before calling any of these functions

version = '4th September 2020'

# Nexus is a registered trademark of the Halliburton Company

import logging

log = logging.getLogger(__name__)
log.debug('keyword_files.py version %s', version)

# defs:
# def substring(shorter, longer):
# def end_of_file(ascii_file):
# def find_keyword(ascii_file, keyword, max_lines = None):
# def skip_blank_lines_and_comments(ascii_file, comment_intro = '!'):
# def split_trailing_comment(line, comment_char = '!'):
# def strip_trailing_comment(line, comment_char = '!'):
# def find_keyword_without_passing(ascii_file, keyword, no_pass_keyword):
# def find_keyword_with_copy(ascii_file_in, keyword, ascii_file_out):
# def find_keyword_pair(ascii_file, primary_keyword, secondary_keyword):
# def find_number(ascii_file):
# def specific_keyword_next(ascii_file, keyword, skip_blank_lines = True, comment_char = '!'):
# def number_next(ascii_file, skip_blank_lines = True, comment_char = '!'):
# def guess_comment_char(ascii_file):


def substring(shorter, longer):
   """Returns True if the first argument is a substring of the second."""
   try:
      longer.index(shorter)
      return True
   except Exception:
      return False


def end_of_file(ascii_file):
   """Returns True if the end of the file has been reached."""
   file_pos = ascii_file.tell()
   line = ascii_file.readline()
   if len(line) == 0:
      return True  # end of file
   ascii_file.seek(file_pos)
   return False


def find_keyword(ascii_file, keyword, max_lines = None):
   """Looks for line starting with given keyword; file pointer is left at start of that line."""
   start_pos = ascii_file.tell()
   while True:
      if max_lines is not None:
         if max_lines <= 0:
            ascii_file.seek(start_pos)
            return False
         max_lines -= 1
      file_pos = ascii_file.tell()
      line = ascii_file.readline()
      if len(line) == 0:
         ascii_file.seek(start_pos)
         return False  # end of file
      words = line.split()
      if len(words) > 0 and words[0].upper() == keyword.upper():
         ascii_file.seek(file_pos)
         return True


def skip_blank_lines_and_comments(ascii_file, comment_char = '!', skip_c_space = True):
   """Skips any lines containing only white space or comment."""
   while True:
      file_pos = ascii_file.tell()
      line = ascii_file.readline()
      if len(line) == 0:
         return  # end of file
      words = line.split()
      if len(words) == 0:
         continue
      if words[0][0] == comment_char:
         continue
      if skip_c_space and len(words[0]) == 1 and (words[0][0] == 'C' or words[0][0] == 'c'):
         continue
      ascii_file.seek(file_pos)
      return


def skip_comments(ascii_file, comment_char = '!', skip_c_space = True):
   """Skips any lines containing only a comment."""
   while True:
      file_pos = ascii_file.tell()
      line = ascii_file.readline()
      if len(line) == 0:
         return  # end of file
      words = line.split()
      if len(words) > 0:  # not a blank line
         if words[0][0] == comment_char:
            continue
         if skip_c_space and len(words[0]) == 1 and (words[0][0] == 'C' or words[0][0] == 'c'):
            continue
      ascii_file.seek(file_pos)
      return


def split_trailing_comment(line, comment_char = '!'):
   """Returns a pair of strings: (line stripped of trailing comment, trailing comment)."""
   # also removes trailing newline
   comment = ''
   local_line = line
   while len(local_line) > 0 and local_line[-1] in ['\n', '\r']:
      local_line = local_line[:-1]
   try:
      pling = local_line.index(comment_char)
      if pling < len(local_line) - 2:
         comment = local_line[pling + 1:]
      return (local_line[:pling], comment)
   except Exception:
      return (local_line, '')


def strip_trailing_comment(line, comment_char = '!'):
   """Returns a copy of line with any trailing comment removed."""
   (result, comment) = split_trailing_comment(line, comment_char = comment_char)
   return result


def find_keyword_without_passing(ascii_file, keyword, no_pass_keyword):
   """Looks for line starting with keyword, but without passing line starting with no_pass_keyword."""
   while True:
      file_pos = ascii_file.tell()
      line = ascii_file.readline()
      if len(line) == 0:
         return False  # end of file
      words = line.split()
      if len(words) > 0:
         if words[0].upper() == keyword.upper():
            ascii_file.seek(file_pos)
            return True
         if words[0].upper() == no_pass_keyword.upper():
            ascii_file.seek(file_pos)
            return False


def find_keyword_with_copy(ascii_file_in, keyword, ascii_file_out):
   """Looks for line starting with given keyword, copying lines in the meantime."""
   while True:
      file_pos = ascii_file_in.tell()
      line = ascii_file_in.readline()
      if len(line) == 0:
         return False  # end of file
      words = line.split()
      if len(words) > 0 and words[0].upper() == keyword.upper():
         ascii_file_in.seek(file_pos)
         return True
      else:
         ascii_file_out.write(line)


def find_keyword_pair(ascii_file, primary_keyword, secondary_keyword):
   """Looks for line starting with a given pair of keywords."""
   while True:
      if not find_keyword(ascii_file, primary_keyword):
         return False
      file_pos = ascii_file.tell()
      line = ascii_file.readline()
      if len(line) == 0:
         return False  # end of file
      words = line.split()
      if len(words) >= 2 and words[1].upper() == secondary_keyword.upper():
         ascii_file.seek(file_pos)
         return True


def find_number(ascii_file):
   """Looks for line starting with any number."""
   while True:
      file_pos = ascii_file.tell()
      line = ascii_file.readline()
      if len(line) == 0:
         return False  # end of file
      words = line.split()
      if len(words) > 0:
         try:
            float(words[0])
         except Exception:  # todo: should only catch a particular exception (type conversion)
            continue
         else:
            ascii_file.seek(file_pos)
            return True


def specific_keyword_next(ascii_file, keyword, skip_blank_lines = True, comment_char = '!'):
   """Returns True if next token in file is the specified keyword."""
   file_pos = ascii_file.tell()  # will restore file pos to original, which may be before blank lines
   while True:
      if (skip_blank_lines):
         file_pos = ascii_file.tell()  # will advance file pos to next non-blank line
      line = ascii_file.readline()
      if len(line) == 0:  # end of file
         ascii_file.seek(file_pos)
         return False
      words = line.split()
      if len(words) == 0 or words[0][0] == comment_char:
         continue  # blank line or comment
      ascii_file.seek(file_pos)  # restore file position whether or not keyword match
      if words[0].upper() == keyword.upper():
         return True
      return False


def number_next(ascii_file, skip_blank_lines = True, comment_char = '!'):
   """Returns True if next token in file is a number."""
   file_pos = ascii_file.tell()
   while True:
      if (skip_blank_lines):
         file_pos = ascii_file.tell()  # will advance file pos to next non-blank line
      line = ascii_file.readline()
      if len(line) == 0:  # end of file
         ascii_file.seek(file_pos)
         return False
      words = line.split()
      if len(words) == 0 or words[0][0] == comment_char:
         continue  # blank line or comment
      ascii_file.seek(file_pos)  # restore file position whether or not number found
      try:
         float(words[0])
      except Exception:  # todo: should only catch a particular exception (type conversion)
         return False
      else:
         return True


def blank_line(ascii_file):
   """Returns True if the next line contains only white space; False otherwise (including comments)."""

   file_pos = ascii_file.tell()
   line = ascii_file.readline()
   ascii_file.seek(file_pos)
   if len(line) == 0:
      return True  # end of file
   words = line.split()
   return len(words) == 0


def guess_comment_char(ascii_file):
   """Returns a string (usually one character) being the guess as to the comment character, or None."""
   file_pos = ascii_file.tell()  # will restore file pos to original
   max_lines = 10
   ch = None
   while max_lines:
      line = ascii_file.readline()
      max_lines -= 1
      if not line:
         break
      words = line.split()
      if len(words) and words[0] in ['!', 'C', '#', '--']:
         ch = words[0]
         break
   ascii_file.seek(file_pos)
   return ch


# end of keyword_files module
