#!/usr/bin/env python
#-*- coding: utf-8 -*-
# Copyright (c) 2007 Jens Kadenbach <[email protected]>
# 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()