# -*- coding: iso-8859-1 -*-
"""
    MoinMoin - Bible Macro 1.1

    Usage:  [[Bible]]                   returns the entire Bible
            [[Bible(Book Ch:Vs)]]
            [[Bible(Book Ch)]]
            [[Bible(Book)]]
            [[Bible(Book1-Book2)]]
            [[Bible(Book Ch1-Ch2)]]
            [[Bible(Book Ch:Vs1-Vs2)]]
            [[Bible(Book Ch1:Vs1-Ch2:Vs2)]]
            [[Bible(Book1 Ch1:Vs1-Book2 Ch2:Vs2)]]
            [[Bible(Bk[:Ch[:Vs]][-[[Bk2:]Ch2:]Vs2])]]
            [[Bible(<header>)]]         returns the Gutenberg header
            [[Bible(<introduction>)]]   returns the Gutenberg notes and introduction
            [[Bible(<contents>)]]       returns the table of contents
            [[Bible(<bible>)]]          returns the unprocessed bible text
            [[Bible(<footer>)]]         returns the Gutenberg footer

    @copyright: 2010 by Kenneth Bull <kenneth_bull@yahoo.com>
    @license: GNU GPL, see COPYING for details.
"""

import re, os
from MoinMoin import config, wikiutil

# Index functions:
class BibleIndex:
    def __init__(self, fname):
        self.f = open(fname, "rb")
        if not self.f:
            raise "Can't open index $fname."
    
    def __del__(self):
        self.f.close()
        
    def GetRootEntry(self, itm = 0):
        self.f.seek(0)
        i = 0
        while 1:
            line = self.f.readline().rstrip('\r\n\t ')
            r = re.match(r"^(.{9}) ([\dabcdef]{8}) ([\dabcdef]{8})$", line, re.I)
            if r:
                if i == itm:
                    return (r.group(1).strip(), int(r.group(2), 16), int(r.group(3), 16))
                i += 1
            else:
                raise "Not found"

    def GetRootEntryByName(self, itm):
        self.f.seek(0)
        while 1:
            line = self.f.readline().rstrip('\r\n\t ')
            r = re.match(r"^(.{9}) ([\dabcdef]{8}) ([\dabcdef]{8})$", line, re.I)
            if r:
                if r.group(1).strip().lower() == itm.lower():
                    return (r.group(1).strip(), int(r.group(2), 16), int(r.group(3), 16))
            else:
                raise "Not found"

    def GetSectionEntry(self, s):
        (jnk, strt, length) = self.GetRootEntryByName("sections")
        self.f.seek(strt)
        while self.f.tell() < strt + length:
            line = self.f.readline().rstrip('\r\n\t ')
            r = re.match(r"^(.{5}) ([\dabcdef]{8}) ([\dabcdef]{8})$", line, re.I)
            if r:
                if r.group(1).strip().lower() == s.lower():
                    return (r.group(1).strip(), int(r.group(2), 16), int(r.group(3), 16))
            else:
                return (0, 0, 0)
        return (0, 0, 0)

    def GetBookNumber(self, bk):
        (jnk, strt, length) = self.GetRootEntryByName("books")

        self.f.seek(strt)
        i = 0
        while self.f.tell() < strt + length:
            line = self.f.readline().rstrip('\r\n\t ')
            r = re.match(r"^(\d{5}) ([\dabcdef]{8}) ([\dabcdef]{8}) (\d{5}) (\d{5}) ?(.*?)$", line, re.I)
            if r:
                if r.group(6).lower() == bk.lower():
                    return i
                i += 1
            else:
                return 0
        return 0

    def GetBookEntry(self, bk):
        (jnk, strt, length) = self.GetRootEntryByName("books")

        self.f.seek(strt+bk*54)
        if self.f.tell() < strt + length:
            line = self.f.readline().rstrip('\r\n\t ')
            r = re.match(r"^(\d{5}) ([\dabcdef]{8}) ([\dabcdef]{8}) (\d{5}) (\d{5}) ?(.*?)$", line, re.I)
            if r and int(r.group(1), 10) == bk:
                return (int(r.group(1), 10), int(r.group(2), 16), int(r.group(3), 16), int(r.group(4), 10), int(r.group(5), 10), r.group(6))
            else:
                return (0,0,0,0,0,"")
        return (0,0,0,0,0,"")

    def GetBookEntryByName(self, bk):
        (jnk, strt, length) = self.GetRootEntryByName("books")

        self.f.seek(strt)
        while self.f.tell() < strt + length:
            line = self.f.readline().rstrip('\r\n\t ')
            r = re.match(r"^(\d{5}) ([\dabcdef]{8}) ([\dabcdef]{8}) (\d{5}) (\d{5}) ?(.*?)$", line, re.I)
            if r:
                if r.group(6).lower() == bk.lower():
                    return (int(r.group(1), 10), int(r.group(2), 16), int(r.group(3), 16), int(r.group(4), 10), int(r.group(5), 10), r.group(6))
            else:
                return (0,0,0,0,0,"")
        return (0,0,0,0,0,"")
        
    def GetChapterEntry(self, bk, ch):
        (bkid, bkstrt, bkend, ch1, nch, name) = self.GetBookEntry(bk)
        (jnk, strt, length) = self.GetRootEntryByName("chapters")

        self.f.seek(strt+(ch1+ch-1)*36)
        if self.f.tell() < strt + length:
            line = self.f.readline().rstrip('\r\n\t ')
            r = re.match(r"^(\d{5}) ([\dabcdef]{8}) ([\dabcdef]{8}) (\d{5}) (\d{5})$", line, re.I)
            if r:
                if int(r.group(1), 10) == ch:
                    return (int(r.group(1), 10), int(r.group(2), 16), int(r.group(3), 16), int(r.group(4), 10), int(r.group(5), 10))
        return (0,0,0,0,0)

    def GetVerseEntry(self, bk, ch, vs):
        (chid, chstrt, chend, vs1, nvs) = self.GetChapterEntry(bk, ch)
        (jnk, strt, length) = self.GetRootEntryByName("verses")

        self.f.seek(strt+(vs1+vs-1)*24)
        if self.f.tell() < strt + length:
            line = self.f.readline().rstrip('\r\n\t ')
            r = re.match(r"^(\d{5}) ([\dabcdef]{8}) ([\dabcdef]{8})$", line, re.I)
            if r and int(r.group(1), 10) == vs:
                return (int(r.group(1), 10), int(r.group(2), 16), int(r.group(3), 16))
        return (0,0,0)

    def RoundVerseDown(self, cite):
        (bk, ch, vs) = cite

        import types
        if isinstance(bk, types.StringType):
            bk = self.GetBookNumber(bk)

        bkmax = (self.GetRootEntryByName("books")[2]-1) / 54
        if bk > bkmax or bk == -1:
            bk = bkmax
            ch = -1
            vs = -1
        if bk == 0:
            bk = 1
            ch = 1
            vs = 1

        chmax = self.GetBookEntry(bk)[4]
        if ch > chmax or ch == -1:
            ch = chmax
            vs = -1
        if ch == 0:
            ch = 1
            vs = 1
        

        vsmax = self.GetChapterEntry(bk, ch)[4]
        if vs > vsmax or vs == -1:
            vs = vsmax

        return (bk, ch, vs or 1)
    
    def RoundVerseUp(self, cite):
        (bk, ch, vs) = cite

        import types
        if isinstance(bk, types.StringType):
            bk = self.GetBookNumber(bk)
        
        bkmax = (self.GetRootEntryByName("books")[2]-1) / 54 - 1
        if bk == 0 or bk > bkmax:
            bk = bkmax
            ch = 0
            vs = 0

        try:
            chmax = self.GetBookEntry(bk)[4]
            if ch == 0 or ch > chmax:
                ch = chmax
                vs = 0
        except:
            print bk

        vsmax = self.GetChapterEntry(bk, ch)[4]
        if vs == 0 or vs > vsmax:
            vs = vsmax

        return (bk, ch, vs)

# Text retrieval
class BibleText:
    def __init__(self, fname):
        self.filename = fname
    
    def __del__(self):
        " do nothing "

    def GetText(self, strt, length):
        f = open(self.filename, "rb")
        f.seek(strt)
        r = f.read(length)
        f.close()
        return r

class Bible:
    def __init__(self, text, index, strt = (0, 0, 0), end = (0, 0, 0)):
        import types
        self.text = BibleText(text)
        self.index = BibleIndex(index)
        import types
        if isinstance(strt, types.TupleType):
            self.type = 1
            self.start = strt
            self.end = end
        else:
            (self.type, self.start, self.end) = self.ParseRef(strt)

    def FixEOL(self, inp):
        return inp.replace("\r\n", "\n")

    def FixVerses(self, inp):
        return re.sub("\n+", "\n", self.FixEOL(inp).replace("\n           ", " "))

    def FormatVerses(self, text):
        html = "<p>"
        text = self.FixVerses(text)
        book = (0, "")
        
        bookre = re.compile(r"^Book (\d\d) {8}(.*)$")
        versere = re.compile(r"^(\d\d)\:(\d\d\d)\:(\d\d\d) (.*)$")
        
        for line in text.split("\n"):
            m = bookre.match(line)
            if m:
                book = (int(m.group(1), 10), m.group(2))
                
            m = versere.match(line)
            if m:
                bk = int(m.group(1), 10)
                ch = int(m.group(2), 10)
                vs = int(m.group(3), 10)

                if vs == 1:
                    if ch == 1:
                        if book[0] == 0:
                            book = (bk, self.index.GetBookEntry(bk)[5])
                        html += """</p>\n<p style='padding-top:1em; text-align:center; font-size:200%%; font-weight:bold;'><a name='bible_%(bk)s'></a>%(title)s</p>\n<p>""" % {'bk': book[0], 'title': book[1]}
                    html += "</p>\n<p><span style='float:left; font-size:200%%;'><a name='bible_%(bk)d_%(ch)d'></a><b>%(ch)d&nbsp;</b></span>\n" % { 'bk': bk, 'ch': ch }
                
                html += "<span><a name='bible_%(bk)d_%(ch)d_%(vs)d'></a><span style='font-size:75%%;'><b><i>%(vs)d</i></b></span> %(txt)s</span>\n" % { 'bk': bk, 'ch': ch, 'vs': vs, 'txt': m.group(4) }
                
        return html+"</p>"

    def GetRaw(self):
        strt = 0
        length = 0
        try:
            if self.type:
                #self.start = self.index.RoundVerseDown(self.start)
                #self.end = self.index.RoundVerseUp(self.end)
            
                (strt,l) = self.index.GetVerseEntry(self.start[0], self.start[1], self.start[2])[1:]
                (e,length) = self.index.GetVerseEntry(self.end[0], self.end[1], self.end[2])[1:]
                if strt > e:
                    tmp = strt
                    strt = e
                    e = tmp
                    tmp = l
                    l = length
                    length = tmp
                length += e - strt
            else:
                (strt, length) = self.index.GetSectionEntry(self.start)[1:]
            return self.text.GetText(strt, length)
        except "Not Found":
            return "Invalid reference"

    def GetText(self):
        return self.type and self.FixVerses(self.GetRaw()) or self.FixEOL(self.GetRaw())

    def GetHTML(self):
        return self.type and self.FormatVerses(self.GetRaw()) or "<pre>"+wikiutil.escape(self.FixEOL(self.GetRaw()))+"</pre>"

    # Reference parser:
    def ParseRef(self, ref):
        ref = ref or ""
        m = re.match(r"^\<(.{4}).*\>$", ref.strip())
        if m:
            return (0, m.group(1), m.group(0))

        m1 = re.match(r"^(.*?)(\-(.*))?$", ref)

        m2 = re.match(r"^(\d+)(?:\:(\d+)(?:\:(\d+))?)?$", m1.group(1).strip())
        if m2:
            if m2.group(1) != None: bk = int(m2.group(1),10)
            else: bk = 0
            if m2.group(2) != None: ch = int(m2.group(2),10)
            else: ch = 0
            if m2.group(3) != None: vs = int(m2.group(3),10)
            else: vs = 0
        else:
            m3 = re.match(r"^(.+?)(?: +(\d+)(?:\:(\d+))?)?$", m1.group(1).strip())
            if m3:
                if m3.group(1) != None: bk = self.index.GetBookNumber(m3.group(1))
                else: bk = 0
                if m3.group(2) != None: ch = int(m3.group(2),10)
                else: ch = 0
                if m3.group(3) != None: vs = int(m3.group(3),10)
                else: vs = 0
            else:
                bk = 0
                ch = 0
                vs = 0

        if bk == 0:
            ch = 0
            vs = 0
        elif ch == 0:
            vs = 0
                
        strt = (bk, ch, vs)

        if m1.group(2):
            if vs:
                m4 = re.match(r"^(?:(?:(\d+)\:)?(\d+)\:)?(\d+)$", m1.group(3).strip())
                if m4:
                    if m4.group(1) != None: bk = int(m4.group(1),10)
                    if m4.group(2) != None: ch = int(m4.group(2),10)
                    elif (bk != strt[0]):   ch = 0
                    if m4.group(3) != None: vs = int(m4.group(3),10)
                    elif (bk != strt[0] or ch != strt[0]): vs = 0
                else:
                    m4 = re.match(r"^(?:(?:(.+?) +)?(\d+)\:)?(\d+)$", m1.group(3).strip())
                    if m4:
                        if m4.group(1) != None: bk = self.index.GetBookNumber(m4.group(1))
                        if m4.group(2) != None: ch = int(m4.group(2),10)
                        elif (bk != strt[0]):   ch = 0
                        if m4.group(3) != None: vs = int(m4.group(3),10)
                        elif (bk != strt[0] or ch != strt[0]): vs = 0
                    else:
                        vs = 0
            elif ch and not vs:
                m4 = re.match(r"^(?:(\d+)\:)?(\d+)(?:\:(\d+))??$", m1.group(3).strip())
                if m4:
                    if m4.group(1) != None: bk = int(m4.group(1),10)
                    if m4.group(2) != None: ch = int(m4.group(2),10)
                    elif (bk != strt[0]):   ch = 0
                    if m4.group(3) != None: vs = int(m4.group(3),10)
                    elif (bk != strt[0] or ch != strt[0]): vs = 0
                else:
                    m4 = re.match(r"^(?:(.+?) +)?(\d+)(?:\:(\d+))?$", m1.group(3).strip())
                    if m4:
                        if m4.group(1) != None: bk = self.index.GetBookNumber(m4.group(1))
                        if m4.group(2) != None: ch = int(m4.group(2),10)
                        elif (bk != strt[0]):   ch = 0
                        if m4.group(3) != None: vs = int(m4.group(3),10)
                        elif (bk != strt[0] or ch != strt[0]): vs = 0
                    else:
                        ch = 0
            elif not ch and not vs:
                m4 = re.match(r"^(\d+)(?:\:(\d+)(?:\:(\d+))?)?$", m1.group(3).strip())
                if m4:
                    if m4.group(1) != None: bk = int(m4.group(1),10)
                    if m4.group(2) != None: ch = int(m4.group(2),10)
                    elif (bk != strt[0]):   ch = 0
                    if m4.group(3) != None: vs = int(m4.group(3),10)
                    elif (bk != strt[0] or ch != strt[0]): vs = 0
                else:
                    m4 = re.match(r"^(.+?)(?: +(\d+)(?:\:(\d+))?)?$", m1.group(3).strip())
                    if m4:
                        if m4.group(1) != None: bk = self.index.GetBookNumber(m4.group(1))
                        if m4.group(2) != None: ch = int(m4.group(2),10)
                        elif (bk != strt[0]):   ch = 0
                        if m4.group(3) != None: vs = int(m4.group(3),10)
                        elif (bk != strt[0] or ch != strt[0]): vs = 0
                    else:
                        bk = 0

        if bk == 0:
            ch = 0
            vs = 0
        elif ch == 0:
            vs = 0
                
        end = (bk, ch, vs)
        #print (1, strt, end)
        strt = self.index.RoundVerseDown(strt)
        end = self.index.RoundVerseUp(end)
        #print (1, strt, end)
        return (1, strt, end)

    def GetReference(self, strt = None, end = None):
        import types
        strt = strt or self.start
        end = end or self.end
        if isinstance(strt, types.TupleType):
            strt = self.index.RoundVerseDown(strt)
            end = self.index.RoundVerseUp(end)
            strtbk = self.index.GetBookEntry(strt[0])
            endbk = self.index.GetBookEntry(end[0])
            endch = self.index.GetChapterEntry(end[0], end[1])
            
            text = strtbk[5]
            if strt[1] > 1 or end[1] < endbk[4] or strt[2] > 1 or end[2] < endch[4]:
                text += " %(ch)d" % {'ch': strt[1]}
                if strt[2] > 1 or end[2] < endch[4]:
                    text += ":%(vs)d" % {'vs': strt[2]}
            if end[0] != strt[0]:
                text += " - %(book)s" % {'book': endbk[5]}
                if strt[1] > 1 or end[1] < endbk[4] or strt[2] > 1 or end[2] < endch[4]:
                    text += " %(ch)d" % {'ch': end[1]}
                    if strt[2] > 1 or end[2] < endch[4]:
                        text += ":%(vs)d" % {'vs': end[2]}
            elif end[1] != strt[1] and (end[1] != endbk[4] or strt[1] != 1 or end[2] != endch[4] or strt[2] != 1):
                text += "-%(ch)d" % {'ch': end[1]}
                if strt[2] > 1 or end[2] < endch[4]:
                    text += ":%(vs)d" % {'vs': end[2]}
            elif end[2] != strt[2] and (end[2] != endch[4] or strt[2] != 1):
                text += "-%(vs)d" % {'vs': end[2]}
            return text
        else:
            return strt

def execute(macro, args):
    bible = Bible(
        os.path.join(macro.request.cfg.data_dir, "bible13.txt"),
        os.path.join(macro.request.cfg.data_dir, "bible13.ndx"),
        args)
    return "<div style='font:serif;'>"+bible.GetHTML()+"<p style='text-align:right;'>"+bible.GetReference()+"</p></div>"
