#!/usr/bin/env python
#-*- coding: utf-8 -*-
# Copyright (c) 2007 Jens Kadenbach <j.kadenbach@gmx.de>
# This program is free software. It comes without any warranty, to
# the extent permitted by applicable law. You can redistribute it
# and/or modify it under the terms of the Do What The Fuck You Want
# To Public License, Version 2, as published by Sam Hocevar. See
# http://sam.zoy.org/wtfpl/COPYING for more details.
"""\
Renumbers and reorders footnotes in a text file.

Task description available here:
http://www.linux-magazin.de/content/download/27388/238793/file/Tasks_languages.pdf
"""

from __future__ import with_statement

import sys
import re
from cStringIO import StringIO
from optparse import OptionParser
from itertools import takewhile, count
from functools import partial

SAMPLE = StringIO('''A great brown fox [13] jumped of a pile of lorem ipsum [4],
[7]. He met with a silver penguin, browsing the Linux Kernel
Mailinglist [3]. They debated whether to start a C-program with
"main (int argc, char **argv)" or with "main (int argc, char *argv[])".
Square brackets annoyed them [9999]. Multiple references exist [4].

@footnote:

[13] Al Fabetus: "On characters and animals", 1888, self published.
[4] Lorem Ipsum, <a href="http://link.org/Lorem_Ipsum">Web
Link</a>
[9999] Annoying Link.
[7] B. Fox: "More on Blind Text".
[3] Linux Kernel Maintainers: LKML
''')

FOOTSPLIT = '@footnote:\n'
NUMBER = re.compile(r'\[(\d+)\]')

def _parse_args():
    parser = OptionParser(usage='%prog [options] FILE', description=__doc__)
    parser.add_option('-t', '--test',
                      help='use test file', action="store_true", dest='test')
    parser.add_option('-a', '--reorder', action="store_true", dest='foot',
                      help='order footnotes in order of appearance in text')
    parser.set_defaults(test=False, foot=False)
    opts, args = parser.parse_args()
    if not len(args) == 1 or opts.test:
        parser.error("File needed!")
    return opts, args[0]

def splitfooter(line):
    """ [3] Linux Kernel Maintainers: LKML
        - > "3"
    """ 
    return line[1:].split(']', 1)

def as_num(line):
    """ key-Function for sorting by
        int-Value of the footer-line
    """
    try:
        start, _ = splitfooter(line)
        return int(start)
    except ValueError:
        # bad line, ignore it
        return None

def create_footer(iterable, order=None):
    """ create and order the footer if
        an order is given, else create
        an order and return footer and order
    """
    if not order:   
        notes = {}
        c = count(1)
        def store(num):
            idx = c.next()
            notes[num] = idx
            return idx
    else:
        def store(num):
            return order[num]
    footer = []
    for line in iterable:
        try:    
            start, content = splitfooter(line)
            num = int(start)
        except ValueError:
            # concat lines of one footer
            try:    
                footer[-1] += line
            except IndexError:
                # happens maybe on first line
                footer.append(line)
        else:   
            idx = store(num)
            footer.append("[%d]%s" % (idx, content))
    if not order:
        # back to start
        return notes, footer
    else:
        return footer
    
def make_replacer(notes=None):  
    """ Creates the regexp-replacer function
        for the body. Gets a footnotes-cache 
        or returns creates one
    """
    def get_val(match): return int(match.group(1))
    if notes:
        def replace_num(match):
            """ replace found value
            """
            n = get_val(match)
            # 0 when not found
            return "[%d]" % notes.get(n, 0)
        return replace_num
    else:
        notes = {}
        c = count(1)
        def replace_num(match):
            """ return new index and assign
                it to current index in notes
            """
            n = get_val(match)
            if not n in notes:
                i = c.next()
                notes[n] = i
            else:
                i = notes[n]
            return "[%d]" % i
        return replace_num, notes
           
# iterator that ends when it encounters FOOTSPLIT
def _end(l) : return not l == FOOTSPLIT
extract_body = partial(takewhile, _end)


def sort_footnotes(fh, sort_footer):
    """ sort_footer: order footnotes by appearance
        in the body
    """
    if not sort_footer:
        # place cursor at the beginning of footer
        for line in extract_body(fh):
            pass
        notes, footer = create_footer(fh)
        replace_num = make_replacer(notes)
        # place cursor at start
        fh.seek(0)
        body = extract_body(fh)
    else:
        body, foot = extract_body(fh), fh
        replace_num, notes = make_replacer()

    for line in body:
        yield NUMBER.sub(replace_num, line)

    if sort_footer:
        footer = sorted(create_footer(foot, order=notes), key=as_num)

    yield FOOTSPLIT
    for line in footer:
        yield line

def main():
    options, input = _parse_args()
    try:    
        fh = SAMPLE if options.test else open(input, 'r')
        sys.stdout.writelines(sort_footnotes(fh, options.foot))
    finally:
        fh.close()
    
if __name__ == '__main__':
    main()

