#! /usr/bin/python
#
# Licensed under the MIT license
# http://opensource.org/licenses/mit-license.php

# Copyright 2006,2007,2008 Frank Scholz <coherence@beebits.net>

""" Coherence is a framework to host DLNA/UPnP devices

    For more information about it and its available backends
    point your browser to: http://coherence-project.org
"""

import os, sys

import string

from coherence import __version__

from coherence.extern.simple_config import Config,ConfigItem

from twisted.python import usage, text


"""
 thankfully taken from twisted.scripts._twistd_unix.py
"""
def daemonize():
    # See http://www.erlenstar.demon.co.uk/unix/faq_toc.html#TOC16
    if os.fork():   # launch child and...
        os._exit(0) # kill off parent
    os.setsid()
    if os.fork():   # launch child and...
        os._exit(0) # kill off parent again.
    os.umask(077)
    null=os.open('/dev/null', os.O_RDWR)
    for i in range(3):
        try:
            os.dup2(null, i)
        except OSError, e:
            if e.errno != errno.EBADF:
                raise
    os.close(null)

"""
 taken with minor adjustments from twisted.python.text.py
"""
def greedyWrap(inString, width=80):
    """Given a string and a column width, return a list of lines.

    Caveat: I'm use a stupid greedy word-wrapping
    algorythm.  I won't put two spaces at the end
    of a sentence.  I don't do full justification.
    And no, I've never even *heard* of hypenation.
    """

    outLines = []

    #eww, evil hacks to allow paragraphs delimited by two \ns :(
    if inString.find('\n\n') >= 0:
        paragraphs = inString.split('\n\n')
        for para in paragraphs:
            outLines.extend(greedyWrap(para, width) + [''])
        return outLines
    inWords = inString.split()

    column = 0
    ptr_line = 0
    while inWords:
        column = column + len(inWords[ptr_line])
        ptr_line = ptr_line + 1

        if (column > width):
            if ptr_line == 1:
                # This single word is too long, it will be the whole line.
                pass
            else:
                # We've gone too far, stop the line one word back.
                ptr_line = ptr_line - 1
            (l, inWords) = (inWords[0:ptr_line], inWords[ptr_line:])
            outLines.append(string.join(l,' '))

            ptr_line = 0
            column = 0
        elif not (len(inWords) > ptr_line):
            # Clean up the last bit.
            outLines.append(' '.join(inWords))
            del inWords[:]
        else:
            # Space
            column = column + 1
    # next word

    return outLines

"""
 taken with minor adjustments from twisted.python.usage.py
"""
def docMakeChunks(optList, width=80):
    """
    Makes doc chunks for option declarations.

    Takes a list of dictionaries, each of which may have one or more
    of the keys 'long', 'short', 'doc', 'default', 'optType'.

    Returns a list of strings.
    The strings may be multiple lines,
    all of them end with a newline.
    """

    # XXX: sanity check to make sure we have a sane combination of keys.

    maxOptLen = 0
    for opt in optList:
        optLen = len(opt.get('long', ''))
        if optLen:
            if opt.get('optType', None) == "parameter":
                # these take up an extra character
                optLen = optLen + 1
            maxOptLen = max(optLen, maxOptLen)

    colWidth1 = maxOptLen + len("  -s, --  ")
    colWidth2 = width - colWidth1
    # XXX - impose some sane minimum limit.
    # Then if we don't have enough room for the option and the doc
    # to share one line, they can take turns on alternating lines.

    colFiller1 = " " * colWidth1

    optChunks = []
    seen = {}
    for opt in optList:
        if opt.get('short', None) in seen or opt.get('long', None) in seen:
            continue
        for x in opt.get('short', None), opt.get('long', None):
            if x is not None:
                seen[x] = 1

        optLines = []
        comma = " "
        if opt.get('short', None):
            short = "-%c" % (opt['short'],)
        else:
            short = ''

        if opt.get('long', None):
            long = opt['long']
            if opt.get("optType", None) == "parameter":
                long = long + '='

            long = "%-*s" % (maxOptLen, long)
            if short:
                comma = ","
        else:
            long = " " * (maxOptLen + len('--'))

        if opt.get('optType', None) == 'command':
            column1 = '    %s      ' % long
        else:
            column1 = "  %2s%c --%s  " % (short, comma, long)

        if opt.get('doc', ''):
            doc = opt['doc'].strip()
        else:
            doc = ''

        if (opt.get("optType", None) == "parameter") \
           and not (opt.get('default', None) is None):
            doc = "%s [default: %s]" % (doc, opt['default'])

        if (opt.get("optType", None) == "parameter") \
           and opt.get('dispatch', None) is not None:
            d = opt['dispatch']
            if isinstance(d, usage.CoerceParameter) and d.doc:
                doc = "%s. %s" % (doc, d.doc)

        if doc:
            column2_l = greedyWrap(doc, colWidth2)
        else:
            column2_l = ['']

        optLines.append("%s%s\n" % (column1, column2_l.pop(0)))

        for line in column2_l:
            optLines.append("%s%s\n" % (colFiller1, line))

        optChunks.append(''.join(optLines))

    return optChunks

usage.docMakeChunks = docMakeChunks

def setConfigFile():
    def findConfigDir():
        try:
            configDir = os.path.expanduser('~')
        except:
            configDir = os.getcwd()
        return configDir

    return os.path.join( findConfigDir(), '.coherence')


class Options(usage.Options):

    optFlags = [['daemon','d', 'daemonize'],
                ['noconfig', None, 'ignore any configfile found'],
                ['version','v', 'print out version']
                ]
    optParameters = [['configfile', 'c', setConfigFile(), 'configfile'],
                     ['logfile', 'l', None, 'logfile'],
                     ['option', 'o', None, 'activate option'],
                     ['plugin', 'p', None, 'activate plugin'],
                    ]

    def __init__(self):
        usage.Options.__init__(self)
        self['plugins'] = []
        self['options'] = {}

    def opt_version(self):
        print "Coherence version:", __version__
        sys.exit(0)

    def opt_help(self):
        sys.argv.remove('--help')
        from coherence.base import Plugins
        p = Plugins()
        for opt,doc in self.docs.items():
            if opt == 'plugin':
                self.docs[opt] = doc + '\n\nExample: --plugin=backend:FSStore,name:MyCoherence\n\nAvailable backends are:\n\n'
                self.docs[opt] += ', '.join(p.keys())

        print self.__str__()
        sys.exit(0)

    def opt_plugin(self,option):
        self['plugins'].append(option)

    def opt_option(self,option):
        try:
            key,value = option.split(':')
            self['options'][key] = value
        except:
            pass

def main(config):

    from coherence.base import Coherence
    c = Coherence(config)
    #c = Coherence(plugins={'FSStore': {'content_directory':'tests/content'},
    #                       'Player': {})
    #c.add_plugin('FSStore', content_directory='tests/content', version=1)


if __name__ == '__main__':

    options = Options()
    try:
        options.parseOptions()
    except usage.UsageError, errortext:
        print '%s: %s' % (sys.argv[0], errortext)
        print '%s: Try --help for usage details.' % (sys.argv[0])
        sys.exit(0)

    try:
        if options['daemon'] == 1:
            daemonize()
    except:
        print traceback.format_exc()

    config = {}

    if options['noconfig'] != 1:
        try:
            config = Config(options['configfile'],root='config').config
        except SyntaxError:
            import traceback
            #print traceback.format_exc()
            try:
                from configobj import ConfigObj
                config = ConfigObj(options['configfile'])
            except:
                print "hmm, seems we are in trouble reading in any sort of config file"
                print traceback.format_exc()
        except IOError:
            print "no config file %r found" % options['configfile']
            pass

    if options['logfile'] != None:
        if isinstance(config,(ConfigItem,dict)):
            if 'logging' not in config:
                config['logging'] = {}
            config['logging']['logfile'] = options['logfile']
        else:
            config['logfile'] = options['logfile']

    for k,v in options['options'].items():
        if k == 'logfile':
            continue
        config[k] = v

    if options['daemon'] == 1:
        if isinstance(config,(ConfigItem,dict)):
            if config.get('logging',None) == None:
                config['logging'] = {}
            if config['logging'].get('logfile',None) == None:
                config['logging']['level'] = 'none'
                try:
                    del config['logging']['logfile']
                except KeyError:
                    pass
        else:
            if config.get('logfile',None) == None:
                config.set('logmode','none')
                try:
                    del config['logfile']
                except KeyError:
                    pass

    if(config.get('use_dbus', 'no') == 'yes' or
       config.get('glib', 'no') == 'yes' or
       config.get('transcoding', 'no') == 'yes'):
        try:
            from twisted.internet import glib2reactor
            glib2reactor.install()
        except AssertionError:
            print "error installing glib2reactor"

    if len(options['plugins']) > 0:
        plugins = config.get('plugin')
        if isinstance(plugins,dict):
            config['plugin']=[plugins]
        if plugins is None:
            plugins = config.get('plugins',None)
        if plugins == None:
            config['plugin'] = []
            plugins = config['plugin']

        while len(options['plugins']) > 0:
            p = options['plugins'].pop()
            plugin = {}
            plugin_conf = p.split(',')
            for pair in plugin_conf:
                pair = pair.split(':',1)
                if len(pair) == 2:
                    pair[0] = pair[0].strip()
                    if pair[0] in plugin:
                        if not isinstance(plugin[pair[0]],list):
                            new_list = [plugin[pair[0]]]
                            plugin[pair[0]] = new_list
                        plugin[pair[0]].append(pair[1])
                    else:
                        plugin[pair[0]] = pair[1]
            try:
                plugins.append(plugin)
            except AttributeError:
                print "mixing commandline plugins and configfile does not work with the old config file format"


    from twisted.internet import reactor

    reactor.callWhenRunning(main, config)
    reactor.run()
