#!/usr/bin/python
'''Print a pinkdex.'''
__version__='$Rev: 600 $'

import sys, os, optparse, re, time

import mitsfs

parser = optparse.OptionParser(usage='usage: %prog [--datadex file]',
                               version='%prog '+'$Id: pinkdb.py 600 2013-01-14 03:14:39Z kcr $')
parser.add_option('-p', '--predicate', dest='predicate',
                  help='SQL predicate for dex', default=None)
parser.add_option('-S', '--shelfcode', dest='shelfcodes', action='append',
                  help='Make a shelfdex', default=[])
parser.add_option('-s', '--supplement', dest='suppl',
                  help='supplementary dex', default=None)
parser.add_option('-o', '--outfile', dest='outfile',
                  help='Output file', default=None)
parser.add_option('-a', '--add', dest='add',
                  help='Datadex format file to merge')
parser.add_option('-H', '--hassle', action='store_true', dest='hassle',
                  help="Stick other holdings in shelfdex for HassleComm's convenience")
parser.add_option('-i', '--inventory', dest='inventory',
                  help='Specify inventory tag')
parser.add_option('-I', '--inventory-description', dest='inventory_desc',
                  help='Specify inventory display name if initializing an inventory tag')
parser.add_option('-d', '--directory', dest='directory',
                  help='Specify target directory')
parser.add_option('-D', '--downcase', action="store_true", dest='downcase', default=False,
                  help='Attempt to downcase the dex')
parser.add_option('-K', '--boxdex', type='int', dest='boxdex')
parser.add_option('--dsn', dest='dsn', default=os.environ.get('MITSFS_DSN') or mitsfs.database_dsn)
options, args = parser.parse_args()

noboxed = True

d = mitsfs.dexdb(dsn = options.dsn)

if options.inventory:
    try:
        inventory = mitsfs.inventory(d, options.inventory, options.inventory_desc)
    except mitsfs.InventoryUnknown, e:
        print 'Unknown inventory %s;\nPlease supply a description with -I so we can initialize it.' % e.code
        sys.exit(1)
else:
    inventory = None

if options.directory:
    if not os.path.isdir(options.directory):
        os.mkdir(options.directory)
    os.chdir(options.directory)

if os.getcwd() == mitsfs.dexbase:
    print 'Preemptively changing directory to /tmp; look for your pinkdexen there.'
    os.chdir('/tmp')

def titlecase(s):
    if options.downcase:
        return re.sub('\'([SDT]|Ll|Re)([^A-Z]|$)', lambda m: m.group(0).lower(), s.title())
    else:
        return s

def nicetitle(line):
    series = [titlecase(i.replace(',',r'\,')) for i in line.series if i]
    titles = [titlecase('=' in i and i[:i.find('=')] or i) for i in line.titles] # strip the sortbys
    if series:
        if len(series) == len(titles):
            titles = ['%s [%s]' % i for i in zip(titles, series)]
        elif len(titles) == 1:
            titles = ['%s [%s]' % (titles[0], '|'.join(series))]
        elif len(series) == 1:
            titles = ['%s [%s]' % (i, series[0]) for i in titles]
        else:  # this is apparently Officially Weird
            print 'Wacky title/series match: ',str(line)
            ntitles = ['%s [%s]' % i for i in zip(titles, series)]
            if len(line.series) < len(titles):
                ntitles += titles[len(series):]
            titles = ntitles
    return '|'.join(titles)

def book(line):
    if noboxed:
        unboxed = [(code, count)
                   for (code, count) in line.codes.items()
                   if mitsfs.codes[mitsfs.splitcode(code)[1]].get('box') not in ('all', 'kbx')]
        nboxed = [(code, count)
                  for (code, count) in line.codes.items()
                  if mitsfs.codes[mitsfs.splitcode(code)[1]].get('box') == 'all']
        if unboxed:
            codes = ','.join((count == 1 and code or r'%s\:%d' % (code, count)
                              for (code, count) in unboxed))
        elif nboxed:
            codes = r'\['+','.join((count == 1 and code or r'%s\:%d' % (code, count)
                                         for (code, count) in nboxed))+r'\]'
        else:
            codes = '*'
    else:
        codes = str(line.codes).replace(':', r'\:')
    args = [mitsfs.texquote(i) for i in [titlecase(line.authortxt), nicetitle(line), codes]]
    return r'\Book'+''.join(['{%s}' % i for i in args])

nwords = {'0': 'zero', '1': 'one', '2': 'two', '3': 'three',
          '4': 'four', '5': 'five', '6': 'six', '7': 'seven',
          '8': 'eight', '9': 'nine'}
def dobar(n):
    return r'\barA'+''.join([r'\bar'+nwords[i] for i in str(n)])+r'\barA'

def progress_meter(it, divisor = 1000):
    count = 0
    for i in it:
        if count % divisor == 0:
            print count
        count += 1
        yield i

def mungedex(query=None, args=[], add=None):
    # 'select distinct title_id from title natural join book where not withdrawn'
    print 'constructing subset:'
    l = list(progress_meter(d.iter(query, args)))
    l = [mitsfs.dexline(i) for i in progress_meter(l)]
    if add:
        dex = mitsfs.dex(l)
        print 'adding %s...' % add
        sys.stdout.flush()
        dex.merge(mitsfs.dex(add))
        l = list(dex)
        print 'done.'

    return l

def mungeshelf(shelfcodes):
    # 'select distinct title_id, count(book_id) from title natural join book where not withdrawn'...'group by title_id'
    query = ('select title_id, book_series_visible, doublecrap, count(book.book_id)' 
             ' from book natural join shelfcode'
             '  left join checkout on book.book_id = checkout.book_id and checkin_stamp is null'
             ' where not withdrawn'
             '  and (' + ' or '.join(['shelfcode=%s']*len(shelfcodes)) + ')'
             '  and checkout_stamp is null'
             ' group by title_id, book_series_visible, doublecrap')
    query = ('select title_id, book_series_visible, doublecrap, count(book.book_id)' 
             ' from book natural join shelfcode'
             ' where not withdrawn'
             '  and (' + ' or '.join(['shelfcode=%s']*len(shelfcodes)) + ')'
             ' group by title_id, book_series_visible, doublecrap')
    args = shelfcodes
    print 'mungeshelf',query
    def constructor(id, bsv, dc, c):
        es = ('@' if bsv else '') + shelfcodes[0] + (dc if dc else '')
        t = mitsfs.title(d, id)
        l = mitsfs.dexline(authors=t.authors,
                           titles=t.titles,
                           series=t.series,
                           codes={es: c})
        l.othercount = sum(t.codes[i] for i in shelfcodes[1:])
        return l.shelfkey(es), l
    print 'constructing subset:'
    l = list(progress_meter(constructor(id, bsv, dc, c) for id, bsv, dc, c in d.getcursor().execute(query, args)))
    print 'sorting:',
    l.sort(key=lambda x: x[0])
    print 'done.'

    return [i[1] for i in l]

def writedex(outfile, books, shelfcode=None, suppl=None, hassle=None, reverse=False, dexname='Pinkdex', letterfield='placeauthor', blob=None, kbx=None):
    if suppl:
        if shelfcode:
            suppl += ' (%s)' % shelfcode
        elif kbx:
            suppl += ' (KBX%s)' % kbx
    elif shelfcode:
        suppl = shelfcode
    elif kbx:
        suppl = 'KBX%s' % kbx
    print 'writing',suppl,'...'
    sys.stdout.flush()
    if outfile is None:
        outfile='pinkdex.tex'
    fp = open(outfile, 'w')
    print >>fp,r'\def\dexname{%s}' % dexname
    if kbx:
        print >>fp,r'\def\Box{1}'
    if shelfcode or kbx:
        print >>fp,r'\def\Reverse{1}'
        print >>fp,r'\def\Shelf{1}'
    elif reverse:
        print >>fp,r'\def\Reverse{}'
    if suppl:
        print >>fp,r'\def\Supple{%s}' % suppl
        print >>fp,r'\def\Period{3}'
    else:
        print >>fp,r'\def\Period{0}'
    print >>fp,r'\input %s/dextex-current.tex' % mitsfs.texbase

    if blob:
        print >>fp,blob
    #print >>fp,r'\input /home/kcr/src/dexcode/dextex-current.tex'
    # 	    ($opt{-by} eq "author" ? () : "\\def\\Reverse{}"),
    # 	    ($opt{-supple} ? "\\def\\Supple{$opt{-supple}}" : ()),
    letter = None

    for line in books:
        newletter = getattr(line, letterfield).upper()[0]
        if letter != newletter:
            if letter is not None and not suppl:
                print >>fp,r'\NextLetter'
            letter = newletter
            print letter,
            sys.stdout.flush()
        if shelfcode or kbx:
            if hassle:
                codes = ' [%s]' % str(line.codes).replace(':', r'\:')
            else:
                codes = ''
            if kbx is None:
                count = line.codes[shelfcode]
            else:
                #bleah
                count = sum([line.codes[i] for i in line.codes if mitsfs.splitcode(i)[2] == str(kbx)])
            if inventory:
                inventory.add(line, count)
            barcode = ''
            if False: # hasattr(line, 'othercount') and line.othercount:
                print >>fp,'\\BookThingy{%s}{%s}{%s}{%s} %% %s' % (mitsfs.texquote(titlecase(line.authortxt)),
                                                         mitsfs.texquote(nicetitle(line)) + codes,
                                                         count - line.othercount, line.othercount, str(line))
            else:
                print >>fp,'\\Book{%s}{%s}{%s} %% %s' % (mitsfs.texquote(titlecase(line.authortxt)),
                                                   mitsfs.texquote(nicetitle(line)) + codes,
                                                   count, str(line))
        else:
            print >>fp,book(line)
    print >>fp,r'\vfill \eject \bye'
    fp.close()
    print 'done.'

if (not inventory) or options.shelfcodes:
    args = []
    if options.predicate:
        predicates.append(options.predicate)

    query = ''

    if options.boxdex is not None:
        print 'boxdex #',options.boxdex
        query += " and shelfcode like 'KBX%%' and doublecrap=%s"
        args.append(str(options.boxdex))
    elif options.shelfcodes:
        #XXX
        ## at, shelfcode, doublecrap = mitsfs.splitcode(options.shelfcode)
        query += ' and (' + ' or '.join(['shelfcode=%s']*len(options.shelfcodes)) + ') '
        args += options.shelfcodes
        ## if at:
        ##     query += ' and book_series_visible'
        ## if doublecrap:
        ##     query += ' and doublecrap=%s'
        ##     args.append(doublecrap)
    else:
        query += " and position('KBX' in shelfcode) = 0"  # filter out KBXen

    if options.predicate:
        query += ' and ' + options.predicate

    if options.shelfcodes:
        books = mungeshelf(options.shelfcodes)
    else:
        query = 'select distinct title_id from title natural join book natural join shelfcode where not withdrawn' + query
        books = mungedex(query, args, options.add)
        print 'sorting dex for pinkdex...',
        sys.stdout.flush()
        books.sort(key=lambda line: (line.placeauthor, line.placetitle))
        print 'done.'

    writedex(options.outfile, books, options.shelfcodes[0] if options.shelfcodes else None, options.suppl, options.hassle, kbx=options.boxdex)

    if not options.outfile and not options.shelfcodes and not options.boxdex:
        print 'sorting dex for titledex...',
        sys.stdout.flush()
        books.sort(key=lambda line: (line.placetitle, line.placeauthor))
        print 'done.'
        writedex('titledex.tex', books, reverse=True, dexname='Titledex', letterfield='placetitle')

        print 'filtering for seriesdex...',
        books = [i for i in books if i.series]
        print 'done. (%d entries)' % len(books)
        print 'sorting for seriesdex...',
        books.sort(key=lambda line: (line.placeseries, line.placetitle, line.placeauthor))
        print 'done.'

        print 'generating serieslist...',
        sl = [r'\beginserieslist']
        for s in d.indices.series.iterkeys():
            l = [mitsfs.dexline(i) for i in d.indices.series[s]]
            sl.append(r'  \Series{%s}{%s}{%d}' % (
                mitsfs.texquote(s),
                mitsfs.texquote(reduce(lambda a, b: a and b,
                                       [line.authors == l[0].authors for line in l])  # are all the authors the same?
                                and l[0].authortxt or 'authorship varies'),
                len(l)
                ))
        sl.append(r'\endserieslist')
        print 'done'

        writedex('seriesdex.tex', books, reverse=True, dexname='Seriesdex', letterfield='placeseries', blob='\n'.join(sl))
        
else:
    # inventory
    timestart = time.time()
    print '; inventory',inventory.code,'run begins',time.ctime(timestart)
    try:
        print 'computing', inventory.code, '...',
        sys.stdout.flush()
        inventory.compute()
        print 'done. (%.2f sec)' % (time.time() - timestart)
        codes = list(inventory.codes())
        print 'Generating shelfdexes for',' '.join(codes)
        for shelfcode in codes:
            print
            print 'Generating shelfdex for',shelfcode
            divisions = list(d.cursor.execute('select title_id'
                                              ' from shelf_divisions natural join shelfcode natural join title_responsibility natural join entity '
                                              ' where inventory_id=%s and shelfcode=%s and order_responsibility_by = 0 order by entity_name',
                                              (inventory.id, shelfcode)))
            if divisions: 
                print 'Shelf divisions:'
                divisions = [mitsfs.title(d, i) for i in divisions]
                for number, line in enumerate(divisions):
                    print line
                    divisions[number] = mitsfs.dexline(line).shelfkey(shelfcode)
                slices = zip([None]+divisions, divisions+[None])

            shelf = list(progress_meter(inventory.shelf(shelfcode)))

            if not shelf:
                print 'empty shelfcode; punting'
                continue

            print 'sorting %s...' % shelfcode,
            sys.stdout.flush()
            shelf.sort(key=lambda tup: tup[0])
            print 'done.'

            slices = []
            if divisions:
                diviter = iter(divisions)
                terminus = diviter.next()
                print 'slicing:'
                for (coordinate, (key, line)) in enumerate(progress_meter(shelf)):
                    if key > terminus:
                        slices.append(coordinate)
                        try:
                            terminus = diviter.next()
                        except StopIteration:
                            break

            if slices:
                slices = [('%s.%02d' % (shelfcode, count), rng)
                          for (count, rng)
                          in enumerate(zip([0] + slices, slices + [len(shelf)]))]
            else:
                slices = [(shelfcode, (0, len(shelf)))]

            slices = [(packet, (start, end))
                      for (packet, (start, end)) in slices
                      if start != end]

            for (packet, (start, end)) in slices:
                inventory.packet(packet, shelfcode)
                print 'shelfdex:', packet
                print start, shelf[start][1]
                print end, shelf[end-1][1]

                outfile='shelfdex-%s.%s.tex' % (inventory.code, packet.replace('/','-'))

                writedex(outfile, (shelf[i][1] for i in xrange(start, end)), shelfcode, inventory.desc+' '+packet, None)
            print 'writing packet order'
            inventory.click()
    finally:
        timeend = time.time()
        print ';'
        print '; inventory',inventory.code,'run ends', time.ctime(timeend)
        print '; duration %.2f seconds' % (timeend - timestart)
        print ';'
