# This file is part of lyx2lyx
# -*- coding: iso-8859-1 -*-
# Copyright (C) 2002 Dekel Tsur <dekel@lyx.org>
# Copyright (C) 2004 José Matos <jamatos@lyx.org>
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.

import string
import re

from parser_tools import find_token, find_token_backwards, get_next_paragraph,\
                         find_tokens, find_end_of_inset, find_re, \
                         is_nonempty_line, get_paragraph, find_nonempty_line, \
                         get_value, get_tabular_lines, check_token, get_layout

floats = {
    "footnote": ["\\begin_inset Foot",
		 "collapsed true"],
    "margin":   ["\\begin_inset Marginal",
		 "collapsed true"],
    "fig":      ["\\begin_inset Float figure",
		 "wide false",
		 "collapsed false"],
    "tab":      ["\\begin_inset Float table",
		 "wide false",
		 "collapsed false"],
    "alg":      ["\\begin_inset Float algorithm",
		 "wide false",
		 "collapsed false"],
    "wide-fig": ["\\begin_inset Float figure",
		 "wide true",
		 "collapsed false"],
    "wide-tab": ["\\begin_inset Float table",
		 "wide true",
		 "collapsed false"]
}

font_tokens = ["\\family", "\\series", "\\shape", "\\size", "\\emph",
	       "\\bar", "\\noun", "\\color", "\\lang", "\\latex"]

pextra_type3_rexp = re.compile(r".*\\pextra_type\s+3")
pextra_rexp = re.compile(r"\\pextra_type\s+(\S+)"+\
			 r"(\s+\\pextra_alignment\s+(\S+))?"+\
			 r"(\s+\\pextra_hfill\s+(\S+))?"+\
			 r"(\s+\\pextra_start_minipage\s+(\S+))?"+\
			 r"(\s+(\\pextra_widthp?)\s+(\S*))?")


def get_width(mo):
    if mo.group(10):
	if mo.group(9) == "\\pextra_widthp":
	    return mo.group(10)+"col%"
	else:
	    return mo.group(10)
    else:
	return "100col%"


#
# Change \begin_float .. \end_float into \begin_inset Float .. \end_inset
#
def remove_oldfloat(file):
    lines = file.body
    i = 0
    while 1:
	i = find_token(lines, "\\begin_float", i)
	if i == -1:
	    break
	# There are no nested floats, so finding the end of the float is simple
	j = find_token(lines, "\\end_float", i+1)

	floattype = string.split(lines[i])[1]
	if not floats.has_key(floattype):
	    file.warning("Error! Unknown float type " + floattype)
	    floattype = "fig"

	# skip \end_deeper tokens
	i2 = i+1
	while check_token(lines[i2], "\\end_deeper"):
	    i2 = i2+1
	if i2 > i+1:
	    j2 = get_next_paragraph(lines, j + 1, file.format + 1)
	    lines[j2:j2] = ["\\end_deeper "]*(i2-(i+1))

	new = floats[floattype]+[""]

	# Check if the float is floatingfigure
	k = find_re(lines, pextra_type3_rexp, i, j)
	if k != -1:
	    mo = pextra_rexp.search(lines[k])
	    width = get_width(mo)
	    lines[k] = re.sub(pextra_rexp, "", lines[k])
	    new = ["\\begin_inset Wrap figure",
		   'width "%s"' % width,
		   "collapsed false",
		   ""]

	new = new+lines[i2:j]+["\\end_inset ", ""]

	# After a float, all font attributes are reseted.
	# We need to output '\foo default' for every attribute foo
	# whose value is not default before the float.
	# The check here is not accurate, but it doesn't matter
	# as extra '\foo default' commands are ignored.
	# In fact, it might be safer to output '\foo default' for all
	# font attributes.
	k = get_paragraph(lines, i, file.format + 1)
	flag = 0
	for token in font_tokens:
	    if find_token(lines, token, k, i) != -1:
		if not flag:
		    # This is not necessary, but we want the output to be
		    # as similar as posible to the lyx format
		    flag = 1
		    new.append("")
		if token == "\\lang":
		    new.append(token+" "+ file.language)
		else:
		    new.append(token+" default ")

	lines[i:j+1] = new
	i = i+1


pextra_type2_rexp = re.compile(r".*\\pextra_type\s+[12]")
pextra_type2_rexp2 = re.compile(r".*(\\layout|\\pextra_type\s+2)")
pextra_widthp = re.compile(r"\\pextra_widthp")

def remove_pextra(file):
    lines = file.body
    i = 0
    flag = 0
    while 1:
	i = find_re(lines, pextra_type2_rexp, i)
	if i == -1:
	    break

        # Sometimes the \pextra_widthp argument comes in it own
        # line. If that happens insert it back in this line.
        if pextra_widthp.search(lines[i+1]):
            lines[i] = lines[i] + ' ' + lines[i+1]
            del lines[i+1]

	mo = pextra_rexp.search(lines[i])
        width = get_width(mo)

        if mo.group(1) == "1":
            # handle \pextra_type 1 (indented paragraph)
            lines[i] = re.sub(pextra_rexp, "\\leftindent "+width+" ", lines[i])
            i = i+1
            continue

        # handle \pextra_type 2 (minipage)
	position = mo.group(3)
	hfill = mo.group(5)
	lines[i] = re.sub(pextra_rexp, "", lines[i])

	start = ["\\begin_inset Minipage",
		 "position " + position,
		 "inner_position 0",
		 'height "0pt"',
		 'width "%s"' % width,
		 "collapsed false"
		 ]
	if flag:
	    flag = 0
	    if hfill:
		start = ["","\hfill",""]+start
	else:
	    start = ['\\layout %s' % file.default_layout,''] + start

	j0 = find_token_backwards(lines,"\\layout", i-1)
	j = get_next_paragraph(lines, i, file.format + 1)

	count = 0
	while 1:
	    # collect more paragraphs to the minipage
	    count = count+1
	    if j == -1 or not check_token(lines[j], "\\layout"):
		break
	    i = find_re(lines, pextra_type2_rexp2, j+1)
	    if i == -1:
		break
	    mo = pextra_rexp.search(lines[i])
	    if not mo:
		break
	    if mo.group(7) == "1":
		flag = 1
		break
	    lines[i] = re.sub(pextra_rexp, "", lines[i])
	    j = find_tokens(lines, ["\\layout", "\\end_float"], i+1)

	mid = lines[j0:j]
	end = ["\\end_inset "]

	lines[j0:j] = start+mid+end
	i = i+1


def is_empty(lines):
    return filter(is_nonempty_line, lines) == []


move_rexp =  re.compile(r"\\(family|series|shape|size|emph|numeric|bar|noun|end_deeper)")
ert_rexp = re.compile(r"\\begin_inset|\\hfill|.*\\SpecialChar")
spchar_rexp = re.compile(r"(.*)(\\SpecialChar.*)")


def remove_oldert(file):
    ert_begin = ["\\begin_inset ERT",
                 "status Collapsed",
                 "",
                 '\\layout %s' % file.default_layout,
                 ""]
    lines = file.body
    i = 0
    while 1:
	i = find_tokens(lines, ["\\latex latex", "\\layout LaTeX"], i)
	if i == -1:
	    break
	j = i+1
	while 1:
            # \end_inset is for ert inside a tabular cell. The other tokens
            # are obvious.
	    j = find_tokens(lines, ["\\latex default", "\\layout", "\\begin_inset", "\\end_inset", "\\end_float", "\\the_end"],
			    j)
	    if check_token(lines[j], "\\begin_inset"):
		j = find_end_of_inset(lines, j)+1
	    else:
		break

	if check_token(lines[j], "\\layout"):
	    while j-1 >= 0 and check_token(lines[j-1], "\\begin_deeper"):
		j = j-1

	# We need to remove insets, special chars & font commands from ERT text
	new = []
	new2 = []
	if check_token(lines[i], "\\layout LaTeX"):
	    new = ['\layout %s' % file.default_layout, "", ""]

	k = i+1
	while 1:
	    k2 = find_re(lines, ert_rexp, k, j)
	    inset = hfill = specialchar = 0
	    if k2 == -1:
		k2 = j
	    elif check_token(lines[k2], "\\begin_inset"):
		inset = 1
            elif check_token(lines[k2], "\\hfill"):
                hfill = 1
                del lines[k2]
                j = j-1
	    else:
		specialchar = 1
		mo = spchar_rexp.match(lines[k2])
		lines[k2] = mo.group(1)
		specialchar_str = mo.group(2)
		k2 = k2+1

	    tmp = []
	    for line in lines[k:k2]:
                # Move some lines outside the ERT inset:
		if move_rexp.match(line):
		    if new2 == []:
			# This is not necessary, but we want the output to be
			# as similar as posible to the lyx format
			new2 = [""]
		    new2.append(line)
		elif not check_token(line, "\\latex"):
		    tmp.append(line)

	    if is_empty(tmp):
		if filter(lambda x:x != "", tmp) != []:
		    if new == []:
			# This is not necessary, but we want the output to be
			# as similar as posible to the lyx format
			lines[i-1] = lines[i-1]+" "
		    else:
			new = new+[" "]
	    else:
		new = new+ert_begin+tmp+["\\end_inset ", ""]

	    if inset:
		k3 = find_end_of_inset(lines, k2)
		new = new+[""]+lines[k2:k3+1]+[""] # Put an empty line after \end_inset
		k = k3+1
		# Skip the empty line after \end_inset
		if not is_nonempty_line(lines[k]):
		    k = k+1
		    new.append("")
            elif hfill:
                new = new + ["\\hfill", ""]
                k = k2
	    elif specialchar:
		if new == []:
		    # This is not necessary, but we want the output to be
		    # as similar as posible to the lyx format
		    lines[i-1] = lines[i-1]+specialchar_str
		    new = [""]
		else:
		    new = new+[specialchar_str, ""]
		k = k2
	    else:
		break

	new = new+new2
	if not check_token(lines[j], "\\latex "):
	    new = new+[""]+[lines[j]]
	lines[i:j+1] = new
	i = i+1

    # Delete remaining "\latex xxx" tokens
    i = 0
    while 1:
	i = find_token(lines, "\\latex ", i)
	if i == -1:
	    break
	del lines[i]


# ERT insert are hidden feature of lyx 1.1.6. This might be removed in the future.
def remove_oldertinset(file):
    lines = file.body
    i = 0
    while 1:
	i = find_token(lines, "\\begin_inset ERT", i)
	if i == -1:
	    break
	j = find_end_of_inset(lines, i)
	k = find_token(lines, "\\layout", i+1)
	l = get_paragraph(lines, i, file.format + 1)
	if lines[k] == lines[l]: # same layout
	    k = k+1
	new = lines[k:j]
	lines[i:j+1] = new
	i = i+1


def is_ert_paragraph(file, i):
    lines = file.body
    if not check_token(lines[i], "\\layout"):
        return 0
    if not file.is_default_layout(get_layout(lines[i], file.default_layout)):
        return 0

    i = find_nonempty_line(lines, i+1)
    if not check_token(lines[i], "\\begin_inset ERT"):
	return 0

    j = find_end_of_inset(lines, i)
    k = find_nonempty_line(lines, j+1)
    return check_token(lines[k], "\\layout")


def combine_ert(file):
    lines = file.body
    i = 0
    while 1:
	i = find_token(lines, "\\begin_inset ERT", i)
	if i == -1:
	    break
	j = get_paragraph(lines, i, file.format + 1)
	count = 0
	text = []
	while is_ert_paragraph(file, j):

	    count = count+1
	    i2 = find_token(lines, "\\layout", j+1)
	    k = find_token(lines, "\\end_inset", i2+1)
	    text = text+lines[i2:k]
	    j = find_token(lines, "\\layout", k+1)
	    if j == -1:
		break

	if count >= 2:
	    j = find_token(lines, "\\layout", i+1)
	    lines[j:k] = text

	i = i+1


oldunits = ["pt", "cm", "in", "text%", "col%"]

def get_length(lines, name, start, end):
    i = find_token(lines, name, start, end)
    if i == -1:
	return ""
    x = string.split(lines[i])
    return x[2]+oldunits[int(x[1])]


def write_attribute(x, token, value):
    if value != "":
	x.append("\t"+token+" "+value)


def remove_figinset(file):
    lines = file.body
    i = 0
    while 1:
	i = find_token(lines, "\\begin_inset Figure", i)
	if i == -1:
	    break
	j = find_end_of_inset(lines, i)

	if ( len(string.split(lines[i])) > 2 ):
	    lyxwidth = string.split(lines[i])[3]+"pt"
	    lyxheight = string.split(lines[i])[4]+"pt"
	else:
	    lyxwidth = ""
	    lyxheight = ""

	filename = get_value(lines, "file", i+1, j)

	width = get_length(lines, "width", i+1, j)
	# what does width=5 mean ?
	height = get_length(lines, "height", i+1, j)
	rotateAngle = get_value(lines, "angle", i+1, j)
	if width == "" and height == "":
	    size_type = "0"
	else:
	    size_type = "1"

	flags = get_value(lines, "flags", i+1, j)
	x = int(flags)%4
	if x == 1:
	    display = "monochrome"
	elif x == 2:
	    display = "gray"
	else:
	    display = "color"

	subcaptionText = ""
	subcaptionLine = find_token(lines, "subcaption", i+1, j)
	if subcaptionLine != -1:
            subcaptionText = lines[subcaptionLine][11:]
	    if subcaptionText != "":
	        subcaptionText = '"'+subcaptionText+'"'

	k = find_token(lines, "subfigure", i+1,j)
	if k == -1:
	    subcaption = 0
	else:
	    subcaption = 1

	new = ["\\begin_inset Graphics FormatVersion 1"]
	write_attribute(new, "filename", filename)
	write_attribute(new, "display", display)
	if subcaption:
	    new.append("\tsubcaption")
	write_attribute(new, "subcaptionText", subcaptionText)
	write_attribute(new, "size_type", size_type)
	write_attribute(new, "width", width)
	write_attribute(new, "height", height)
	if rotateAngle != "":
	    new.append("\trotate")
	    write_attribute(new, "rotateAngle", rotateAngle)
	write_attribute(new, "rotateOrigin", "leftBaseline")
	write_attribute(new, "lyxsize_type", "1")
	write_attribute(new, "lyxwidth", lyxwidth)
	write_attribute(new, "lyxheight", lyxheight)
	new = new + ["\\end_inset"]
	lines[i:j+1] = new


##
# Convert tabular format 2 to 3
#
attr_re = re.compile(r' \w*="(false|0|)"')
line_re = re.compile(r'<(features|column|row|cell)')

def update_tabular(file):
    regexp = re.compile(r'^\\begin_inset\s+Tabular')
    lines = file.body
    i = 0
    while 1:
        i = find_re(lines, regexp, i)
        if i == -1:
            break

	for k in get_tabular_lines(lines, i):
	    if check_token(lines[k], "<lyxtabular"):
		lines[k] = string.replace(lines[k], 'version="2"', 'version="3"')
	    elif check_token(lines[k], "<column"):
		lines[k] = string.replace(lines[k], 'width=""', 'width="0pt"')

	    if line_re.match(lines[k]):
		lines[k] = re.sub(attr_re, "", lines[k])

	i = i+1


##
# Convert tabular format 2 to 3
#
# compatibility read for old longtable options. Now we can make any
# row part of the header/footer type we want before it was strict
# sequential from the first row down (as LaTeX does it!). So now when
# we find a header/footer line we have to go up the rows and set it
# on all preceding rows till the first or one with already a h/f option
# set. If we find a firstheader on the same line as a header or a
# lastfooter on the same line as a footer then this should be set empty.
# (Jug 20011220)

# just for compatibility with old python versions
# python >= 2.3 has real booleans (False and True)
false = 0
true = 1

# simple data structure to deal with long table info
class row:
    def __init__(self):
        self.endhead = false		# header row
        self.endfirsthead = false	# first header row
        self.endfoot = false		# footer row
        self.endlastfoot = false	# last footer row


def haveLTFoot(row_info):
    for row_ in row_info:
        if row_.endfoot:
            return true
    return false


def setHeaderFooterRows(hr, fhr, fr, lfr, rows_, row_info):
    endfirsthead_empty = false
    endlastfoot_empty = false
    # set header info
    while (hr > 0):
        hr = hr - 1
        row_info[hr].endhead = true

    # set firstheader info
    if fhr and fhr < rows_:
        if row_info[fhr].endhead:
            while fhr > 0:
                fhr = fhr - 1
                row_info[fhr].endfirsthead = true
                row_info[fhr].endhead = false
        elif row_info[fhr - 1].endhead:
            endfirsthead_empty = true
        else:
            while fhr > 0 and not row_info[fhr - 1].endhead:
                fhr = fhr - 1
                row_info[fhr].endfirsthead = true

    # set footer info
    if fr and fr < rows_:
        if row_info[fr].endhead and row_info[fr - 1].endhead:
            while fr > 0 and not row_info[fr - 1].endhead:
                fr = fr - 1
                row_info[fr].endfoot = true
                row_info[fr].endhead = false
        elif row_info[fr].endfirsthead and row_info[fr - 1].endfirsthead:
            while fr > 0 and not row_info[fr - 1].endfirsthead:
                fr = fr - 1
                row_info[fr].endfoot = true
                row_info[fr].endfirsthead = false
        elif not row_info[fr - 1].endhead and not row_info[fr - 1].endfirsthead:
            while fr > 0 and not row_info[fr - 1].endhead and not row_info[fr - 1].endfirsthead:
                fr = fr - 1
                row_info[fr].endfoot = true

    # set lastfooter info
    if lfr and lfr < rows_:
        if row_info[lfr].endhead and row_info[lfr - 1].endhead:
            while lfr > 0 and not row_info[lfr - 1].endhead:
                lfr = lfr - 1
                row_info[lfr].endlastfoot = true
                row_info[lfr].endhead = false
        elif row_info[lfr].endfirsthead and row_info[lfr - 1].endfirsthead:
            while lfr > 0 and not row_info[lfr - 1].endfirsthead:
                lfr = lfr - 1
                row_info[lfr].endlastfoot = true
                row_info[lfr].endfirsthead = false
        elif row_info[lfr].endfoot and row_info[lfr - 1].endfoot:
            while lfr > 0 and not row_info[lfr - 1].endfoot:
                lfr = lfr - 1
                row_info[lfr].endlastfoot = true
                row_info[lfr].endfoot = false
        elif not row_info[fr - 1].endhead and not row_info[fr - 1].endfirsthead and not row_info[fr - 1].endfoot:
            while lfr > 0 and not row_info[lfr - 1].endhead and not row_info[lfr - 1].endfirsthead and not row_info[lfr - 1].endfoot:
                lfr = lfr - 1
                row_info[lfr].endlastfoot = true
        elif haveLTFoot(row_info):
            endlastfoot_empty = true

    return endfirsthead_empty, endlastfoot_empty


def insert_attribute(lines, i, attribute):
    last = string.find(lines[i],'>')
    lines[i] = lines[i][:last] + ' ' + attribute + lines[i][last:]


rows_re = re.compile(r'rows="(\d*)"')
longtable_re = re.compile(r'islongtable="(\w)"')
ltvalues_re = re.compile(r'endhead="(-?\d*)" endfirsthead="(-?\d*)" endfoot="(-?\d*)" endlastfoot="(-?\d*)"')
lt_features_re = re.compile(r'(endhead="-?\d*" endfirsthead="-?\d*" endfoot="-?\d*" endlastfoot="-?\d*")')
def update_longtables(file):
    regexp = re.compile(r'^\\begin_inset\s+Tabular')
    body = file.body
    i = 0
    while 1:
        i = find_re(body, regexp, i)
        if i == -1:
            break
        i = i + 1
        i = find_token(body, "<lyxtabular", i)
        if i == -1:
            break

        # get number of rows in the table
        rows = int(rows_re.search(body[i]).group(1))

        i = i + 1
        i = find_token(body, '<features', i)
        if i == -1:
            break

        # is this a longtable?
        longtable = longtable_re.search(body[i])

        if not longtable:
            # islongtable is missing add it
            body[i] = body[i][:10] + 'islongtable="false" ' + body[i][10:]

        if not longtable or longtable.group(1) != "true":
            # remove longtable elements from features
            features = lt_features_re.search(body[i])
            if features:
                body[i] = string.replace(body[i], features.group(1), "")
            continue

        row_info = row() * rows
        res = ltvalues_re.search(body[i])
        if not res:
            continue

        endfirsthead_empty, endlastfoot_empty = setHeaderFooterRows(res.group(1), res.group(2), res.group(3), res.group(4), rows, row_info)

        if endfirsthead_empty:
            insert_attribute(body, i, 'firstHeadEmpty="true"')

        if endfirsthead_empty:
            insert_attribute(body, i, 'lastFootEmpty="true"')

        i = i + 1
        for j in range(rows):
            i = find_token(body, '<row', i)

            self.endfoot = false		# footer row
            self.endlastfoot = false	# last footer row
            if row_info[j].endhead:
                insert_attribute(body, i, 'endhead="true"')

            if row_info[j].endfirsthead:
                insert_attribute(body, i, 'endfirsthead="true"')

            if row_info[j].endfoot:
                insert_attribute(body, i, 'endfoot="true"')

            if row_info[j].endlastfoot:
                insert_attribute(body, i, 'endlastfoot="true"')

            i = i + 1


# Figure insert are hidden feature of lyx 1.1.6. This might be removed in the future.
def fix_oldfloatinset(file):
    lines = file.body
    i = 0
    while 1:
	i = find_token(lines, "\\begin_inset Float ", i)
	if i == -1:
	    break
        j = find_token(lines, "collapsed", i)
        if j != -1:
            lines[j:j] = ["wide false"]
        i = i+1


def change_listof(file):
    lines = file.body
    i = 0
    while 1:
	i = find_token(lines, "\\begin_inset LatexCommand \\listof", i)
	if i == -1:
	    break
        type = re.search(r"listof(\w*)", lines[i]).group(1)[:-1]
        lines[i] = "\\begin_inset FloatList "+type
        i = i+1


def change_infoinset(file):
    lines = file.body
    i = 0
    while 1:
        i = find_token(lines, "\\begin_inset Info", i)
        if i == -1:
            break
        txt = string.lstrip(lines[i][18:])
        new = ["\\begin_inset Note", "collapsed true", ""]
        j = find_token(lines, "\\end_inset", i)
        if j == -1:
            break

        note_lines = lines[i+1:j]
        if len(txt) > 0:
            note_lines = [txt]+note_lines

        for line in note_lines:
            new = new + ['\layout %s' % file.default_layout, ""]
            tmp = string.split(line, '\\')
            new = new + [tmp[0]]
            for x in tmp[1:]:
                new = new + ["\\backslash ", x]
        lines[i:j] = new
        i = i+5


def change_header(file):
    lines = file.header
    i = find_token(lines, "\\use_amsmath", 0)
    if i == -1:
	return
    lines[i+1:i+1] = ["\\use_natbib 0",
		      "\use_numerical_citations 0"]


convert = [[220, [change_header, change_listof, fix_oldfloatinset,
                  update_tabular, update_longtables, remove_pextra,
                  remove_oldfloat, remove_figinset, remove_oldertinset,
                  remove_oldert, combine_ert, change_infoinset]]]
revert  = []


if __name__ == "__main__":
    pass
