# -*- coding: utf-8 -*-

# file prefs2prefs.py
# This file is part of LyX, the document processor.
# Licence details can be found in the file COPYING.

# author Richard Heck

# Full author contact details are available in file CREDITS

# This is the main file for the user preferences conversion system.
# There are two subsidiary files:
#     prefs2prefs_lfuns.py
#     prefs2prefs_prefs.py
# The former is used to convert bind and ui files; the latter, to convert
# the preferences file.
#
# I've organized it this way because, in many ways, converting bind and ui
# files  lfuns) and converting the preferences file are the same task. It's
# very line-by-line, unlike lyx2lyx and layout2layout, where changes can be
# more "global". So we read the file, line by line, and give a bunch of
# converter functions a chance to see if they want to modify that line.

# The converter functions are all in the subsidiary files. They take a line
# as  argument and return a list: (Bool, NewLine), where the Bool says if
# we've modified anything and the NewLine is the new line, if so, which will
# be used to replace the old line.

# The format of the existing files is format 0, as of 2.0.alpha6. We'll
# introduce new format numbers as we proceed, just as with layout2layout.
# These will be different for the bind and ui files and for the preferences
# file.

from __future__ import print_function
import os, re, string, sys
from getopt import getopt
import io

###########################################################
# Utility functions, borrowed from layout2layout.py

def trim_bom(line):
    " Remove byte order mark."
    if line[0:3] == u"\357\273\277":
        return line[3:]
    else:
        return     line


def read(source):
    " Read input file and strip lineendings."
    lines = source.read().splitlines() or ['']
    lines[0] = trim_bom(lines[0])
    return lines


def write(output, lines):
    " Write output file with native lineendings."
    output.write(os.linesep.join(lines) + os.linesep)


# for use by find_format_lines
re_comment = re.compile(r'^#')
re_empty   = re.compile(r'^\s*$')

def find_format_line(lines):
    '''
    Returns (bool, int), where int is number of the line the `Format'
    specification is on, or else the number of the first non-blank,
    non-comment line. The bool tells whether we found a format line.
    '''
    for i in range(len(lines)):
        l = lines[i]
        if re_comment.search(l) or re_empty.search(l):
            continue
        m = re_format.search(l)
        if m:
            return (True, i)
        # we're done when we have hit a non-comment, non-empty line
        break
    return (False, i)


# for use by get_format
re_format  = re.compile(r'^Format\s+(\d+)\s*$')

def get_format(lines):
    " Gets format of current file and replaces the format line with a new one "
    (found, format_line) = find_format_line(lines)
    if not found:
        return 0
    line = lines[format_line]
    m = re_format.search(line)
    if not m:
        sys.stderr.write("Couldn't match format line!\n" + line + "\n")
        sys.exit(1)
    return int(m.group(1))


def update_format(lines):
    " Writes new format line "
    (found, format_line) = find_format_line(lines)
    if not found:
        lines[format_line:format_line] = ("Format 1", "")
        return

    line = lines[format_line]
    m = re_format.search(line)
    if not m:
        sys.stderr.write("Couldn't match format line!\n" + line + "\n")
        sys.exit(1)
    format = int(m.group(1))
    lines[format_line] = "Format " + str(format + 1)


def abort(msg):
    sys.stderr.write("\n%s\n" % (msg))
    sys.exit(10)

#
###########################################################

def usage():
    print ("%s [-l] [-p] infile outfile" % sys.argv[0])
    print ("or: %s [-l] [-p] <infile >outfile" % sys.argv[0])
    print ("  -l: convert LFUNs (bind and ui files)")
    print ("  -p: convert preferences")
    print ("Note that exactly one of -l and -p is required.")


def main(argv):
    try:
        (options, args) = getopt(sys.argv[1:], "lp")
    except:
        usage()
        abort("Unrecognized option")

    opened_files = False
    # Open files
    if len(args) == 0:
        source = sys.stdin
        output = sys.stdout
    elif len(args) == 2:
        source = io.open(args[0], 'r', encoding='utf_8', errors='surrogateescape')
        output = io.open(args[1], 'w', encoding='utf_8', newline='\n')
        opened_files = True
    else:
        usage()
        abort("Either zero or two arguments must be given.")

    conversions = False

    for (opt, param) in options:
        if opt == "-l":
            from prefs2prefs_lfuns import conversions
        elif opt == "-p":
            from prefs2prefs_prefs import conversions

    if not conversions:
        usage()
        abort("Neither -l nor -p given.")
    elif len(options) > 1:
        usage()
        abort("Only one of -l or -p should be given.")

    current_format = len(conversions)
    lines = read(source)
    format = get_format(lines)

    while format < current_format:
        target_format, convert = conversions[format]
        old_format = format

        # make sure the conversion list is sequential
        if int(old_format) + 1 != target_format:
            abort("Something is wrong with the conversion chain.")

        for c in convert:
            for i in range(len(lines)):
                (update, newline) = c(lines[i])
                if update:
                    lines[i] = newline

        update_format(lines)
        format = get_format(lines)

        # sanity check
        if int(old_format) + 1 != int(format):
            abort("Failed to convert to new format!")

    write(output, lines)

    # Close files
    if opened_files:
        source.close()
        output.close()

    return 0


if __name__ == "__main__":
    main(sys.argv)
