#!/usr/bin/env python
import os
import sys
import logging
import inspect
import tempfile
import re


logger = logging.getLogger(__name__)

try:
    import subprocess
except ImportError:
    subprocess = None

PY3 = sys.version_info > ( 3, 0, 0 )

if PY3:
    unicode = str
    basestring = (str,)


def partition(text, sep):
    if hasattr(str, 'partition'):
        return text.partition(sep)
    if not sep in text:
        return (text, '', '')
    data = text.split(sep)
    return (data[0], sep, sep.join(data[1:]))


def npath(*args):
    x = os.path.join(*args)
    x = os.path.expanduser(x)
    x = os.path.normpath(x)
    x = os.path.abspath(x)
    return x


def mkstemp(mode='wb'):
    fd, name = tempfile.mkstemp()
    os.close(fd)
    return open(name, mode)


class RunError(Exception):
    def __init__(self, cmd, returncode, out, err):
        self.cmd, self.returncode, self.out, self.err = cmd, returncode, out, err
        msg = []
        msg += [ 'failed to run (retcode %i): %s' %( self.returncode, ' '.join(cmd)), ]
        msg += [ 'stderr:', ]
        msg.extend([ ' '*3+ '.' + l for l in err.split('\n') ])
        msg += [ 'stdout:', ]
        msg.extend([ ' '*3+ '.' +l for l in out.split('\n') ])
        super(LaunchError, self).__init__( '\n'.join(msg) )


def run(cmd):
    out = mkstemp()
    err = mkstemp()
    oname = out.name
    ename = err.name
    try:
        if subprocess:
            p = subprocess.Popen(cmd, stdout=out, stderr=err)
            p.communicate()
            out.flush(); out.close()
            err.flush(); err.close()
            retcode = p.returncode
        else:
            command = []
            for c in cmd:
                if ' ' in c:
                    command.append( '"%s"' % c )
                else:
                    command.append( c )
            command = ' '.join(command)
            command += ' >"%s"' % oname
            command += ' 2>"%s"' % ename
            p = os.popen(command, 'w' )
            retcode = p.close()
            if retcode == None:
                retcode = 0
        if retcode != 0:
            raise RunError(cmd, p.returncode, 
                            open(oname, 'rb').read(), 
                            open(ename, 'rb').read())
    except:
        os.unlink(oname)
        os.unlink(ename)
        raise

    text = open(oname, 'rb').read()
    result = unicode(text, encoding=sys.getfilesystemencoding())
    os.unlink(oname)
    os.unlink(ename)
    return result


def exists(filename, root=None):
    if root:
        filename = npath(root, filename)
    else:
        filename = npath('/', filename)
    if os.path.exists(filename):
        return filename
    else:
        return None


def scat(filename, root=None):
    if root:
        filename = npath(root, filename)
    else:
        filename = npath('/', filename)
    text = open(filename, 'rb').read()
    try:
        text = unicode(text, encoding=sys.getfilesystemencoding())
    except UnicodeDecodeError:
        text = unicode(text, encoding='utf-8')
    return text.split('\n')


def parse_simple_conf( conf ):
    result = {}
    for line in open(conf, 'rb'):
        if not line.strip(): continue
        if not '=' in line: continue
        key, value = line[:line.index('=')], line[line.index('=')+1:].strip().strip('"')
        result[key] = value
    return result


def sscan(expr, filename, root=None):
    if isinstance(expr, basestring):
        expr = re.compile(expr)

    result = []
    for l in scat(filename, root):
        m = expr.search(l)
        if m:
            result.append(m.group('var'))
    return result      



class Detect(object):
    def __init__(self):
        self._mode = None
        self.value = None
    def __nonzero__(self):
        return bool(self._mode)
    
    def _mode_get( self ):
        return self._mode
    def _mode_set( self, value ):
        if value in [ 'heuristic', 'runtime', 'fixed', None, ]:
            self._mode = value
        else:
            raise ValueError('invalid value for mode')
    mode = property(_mode_get, _mode_set)


def get_platform(root=None, noext=False):
    result = Detect()
    for n in [ 
               'etc/centos-release',
               'etc/fedora-release',
               'etc/redhat-release',
               'etc/mandrake-release',
               'etc/SuSE-release',
               'etc/os-release',
           ]:
        fullpath = npath('/', n)
        if root:
            fullpath = npath(root, n)
        if os.path.exists(fullpath):
            result.mode, result.value = 'heuristic', 'linux'
            logger.debug('platform detection (heuristic) using "%s"' % fullpath)
            for line in scat(fullpath):
                logger.debug('.%s' % line)
            break


    if noext:
        return result

    out = run([ 'uname', '-s']).strip().upper()
    logger.debug('platform detection (runtime) using "%s"' % out)
    if 'LINUX' in out:
        result.mode, result.value = 'runtime', 'linux'
    elif 'OPENBSD' in out:
        result.mode, result.value = 'runtime', 'openbsd'
    elif 'FREEBSD' in out:
        result.mode, result.value = 'runtime', 'freebsd'
    elif 'DARWIN' in out:
        result.mode, result.value = 'runtime', 'darwin'
    
    return result
                

def get_distro(platform, root=None, noext=False):
    logger = logging.getLogger('distro')
    result = Detect()


    if platform == 'darwin':
        result.mode, result.value = 'fixed', 'macosx'
    elif platform == 'linux':
        relfiles = [ 
                     'etc/centos-release',
                     'etc/fedora-release',
                     'etc/redhat-release',
                     'etc/mandrake-release',
                     'etc/SuSE-release',
                     'etc/os-release',
                   ]
        for relfile in relfiles:
            fullpath = npath('/', relfile)
            if root:
                fullpath = npath(root, relfile)
            if not os.path.exists(fullpath):
                continue

            if relfile == 'etc/SuSE-release':
                for line in scat(fullpath): logger.debug('.%s' % line)
                result.mode, result.value = 'heuristic', 'suse'
                tag = scat(fullpath)[-1]
                if [ x for x in scat(fullpath) if 'SUSE Linux Enterprise Server' in x ]:
                    result.mode, result.value = 'heuristic', 'sle'
                break
            if relfile == 'etc/mandrake-release':
                for line in scat(fullpath): logger.debug('.%s' % line)
                result.mode, result.value = 'heuristic', 'mandrake'
                break
            if relfile == 'etc/fedora-release':
                for line in scat(fullpath): logger.debug('.%s' % line)
                result.mode, result.value = 'heuristic', 'fedora'
                break
            if relfile == 'etc/centos' or relfile == 'etc/centos-release':
                for line in scat(fullpath): logger.debug('.%s' % line)
                result.mode, result.value = 'heuristic', 'centos'
                break
            if relfile == 'etc/redhat-release':
                for line in scat(fullpath): logger.debug('.%s' % line)
                result.mode, result.value = 'heuristic', 'redhat'
                if [ x for x in scat(fullpath) if 'Red Hat Enterprise Linux ' in x]:
                    result.mode, result.value = 'heuristic', 'rhel'
                elif [ x for x in scat(fullpath) if 'CentOS release' in x]:
                    result.mode, result.value = 'heuristic', 'centos'
                break
            if relfile == 'etc/slackware-release':
                for line in scat(fullpath): logger.debug('.%s' % line)
                result.mode, result.value = 'heuristic', 'slackware'
                break
            if relfile == 'etc/xandros-desktop-version':
                for line in scat(fullpath): logger.debug('.%s' % line)
                result.mode, result.value = 'heuristic', 'xandros'
                break

    if not result and exists('etc/SuSEconfig'):
        result.mode, result.value = 'heuristic', 'sle'
        

    if noext:
        return result

    return result


def get_version(platform, distro, root=None, noext=False):
    result = Detect()

    if (platform, distro) == ( 'linux', 'suse', ):
        result.mode, result.value = ( 'heuristic',
            [ l.split()[1] for l in scat('etc/SuSE-release', root) if l.startswith('openSUSE') ][-1], )
    if (platform, distro) == ( 'linux', 'sle', ):
        result.mode, result.value = ( 'heuristic',
            [ l.split('=')[1].strip() for l in scat('etc/SuSE-release', root) if l.startswith('VERSION') ][-1], )
        pl = [ l.split('=')[1].strip() for l in scat('etc/SuSE-release', root) if l.startswith('PATCHLEVEL') ]
        if pl:
            result.value += '.' + pl[-1].strip()
    if (platform, distro) == ( 'linux', 'fedora', ):
        if exists('etc/os-release', root):
            result.mode, result.value = ( 'heuristic',
                [ l.split('=')[1] for l in scat('etc/os-release', root) if l.startswith('VERSION_ID') ][-1], )
    if (platform, distro) == ( 'linux', 'rhel', ):
        if exists('etc/redhat-release', root):
            values = sscan( r'Red Hat Enterprise Linux (ES release|Server|Server release|AS release) (?P<var>\d+([.]\d+)*)', 'etc/redhat-release', root)
            if values:
                result.mode, result.value = ( 'heuristic', values[-1], )
    if (platform, distro) == ( 'linux', 'centos', ):
        if exists('etc/centos-release', root):
            tag = scat('etc/centos-release', root)[0]
            m = re.search( r'CentOS Linux release (?P<version>\d([.]\d))(?P<rev>[.]\d)', tag )
            logger.debug('1. checking centos with tag [%s], %s' % (tag, { None: 'failed' }.get(m, 'success')) )
            if m: result.mode, result.value = ( 'heuristic', m.group('version'))
        if not result and exists('etc/redhat-release', root):
            tag = scat('etc/redhat-release', root)[0]
            m = re.search( r'CentOS release (?P<version>\d+([.]\d+)?)', tag )
            logger.debug('2. checking centos with tag [%s], %s' % (tag, { None: 'failed' }.get(m, 'success')) )
            if m: result.mode, result.value = ( 'heuristic', m.group('version'))
            

    if noext:
        return result

    if (platform, distro) == ( 'darwin', 'macosx', ):
        out = [ n for n in run([ 'sw_vers', ]).split('\n') if n.strip().startswith('ProductVersion:') ]
        if out:
            result.mode, result.value = 'runtime', out[0].split(':')[1].strip()

    return result
           

def get_arch(platform, distro, version, root=None, noext=False):
    result = Detect()

    if (platform, distro) == ( 'linux', 'suse', ):
        result.mode, result.value = ( 'heuristic',
            [ l.split('(')[1].strip() for l in scat('etc/SuSE-release', root) if l.startswith('openSUSE') ][-1], )
        if result.value == 'i586':
            result.value = 'i686'

    if noext:
        return result

    result.mode, result.value = 'runtime', run(['uname', '-m',]).strip()
    return result


def systemdata(root, noext, astag=False):
    result = get_platform(root, noext)
    logger.info('platform = "%s" using %s' % (result.value, result.mode) )
    platform = result.value

    result = get_distro(platform, root, noext)
    logger.info('distro = "%s" using %s' % (result.value, result.mode) )
    distro = result.value

    result = get_version(platform, distro, root, noext)
    logger.info('version = "%s" using %s' % (result.value, result.mode) )
    version = result.value

    result = get_arch(platform, distro, version, root, noext)
    logger.info('arch = "%s" using %s' % (result.value, result.mode) )
    arch = result.value

    if astag:
        if hasattr(str, 'format'):
            fmt = "{platform}-{distro}-{version}-{arch}"
            return fmt.format(platform=platform, distro=distro, version=version, arch=arch)
        else:
            fmt = "%(platform)s-%(distro)s-%(version)s-%(arch)s"
            return fmt % {'platform' : platform, 'distro' : distro, 'version' : version, 'arch' :arch, }
    else:
        return platform, distro, version, arch


def show_help( abort=None ):
    if abort:
        msg = "invalid command: %s" % abort
    else:
        msg = '''
ident3.py [options] 

options:

    -v|--verbose    display more message
    -d|--debug      tracing info

'''
    sys.stderr.write(msg)
    ret = 0
    if abort:
        ret = 1
    sys.exit(ret)


def parse_args(args):
    logging.basicConfig()
    logging.getLogger().setLevel(logging.WARNING)

    class XFormatter(logging.Formatter):
        def format(self, record):
            if record.levelno == 0:
                record.levelname = 'NOT'
            elif record.levelno == 10:
                record.levelname = 'DBG'
            elif record.levelno == 20:
                record.levelname = 'INF'
            elif record.levelno == 30:
                record.levelname = 'WNG'
            elif record.levelno == 40:
                record.levelname = 'ERR'
            else:
                record.levelname = 'CRT'
            record.funcName = inspect.stack()[8][3]
            return logging.Formatter.format(self, record)

    fmt = XFormatter(fmt='%(levelname)s:%(funcName)s:%(message)s')
    logging.getLogger().handlers[0].setFormatter( fmt )
    

    class Options(object): pass
    result = Options()
    result.args = []
    result.root = None
    result.noext = False
    i = 0
    while i < len(args):
        a = args[i]
        if a in [ '-h', '--help' ]:
            show_help()
            i += 1; continue
        if a in [ '-v', '--verbose' ]:
            logging.getLogger().setLevel(logging.INFO)
            i += 1; continue
        if a in [ '-d', '--debug' ]:
            logging.getLogger().setLevel(logging.DEBUG)
            i += 1; continue
        if a in [ '-R', '--root', ]:
            result.root = args[i+1]
            i += 2; continue
        if a in [ '-n', '-no-external' ]:
            result.noext = True
            i += 1; continue
        result.args.append(a)
        i += 1
    return result
 

def main(args):
    options = parse_args(args)
    logger.debug('using python "%s"' % (str(sys.version_info)))
    logger.debug('filesystem encoding "%s"' % (str(sys.getfilesystemencoding())))
    
    platform, distro, version, arch = systemdata(options.root, options.noext)   

    if hasattr(str, 'format'):
        fmt = "{platform}-{distro}-{version}-{arch}"
        print(fmt.format(platform=platform, distro=distro, version=version, arch=arch))
    else:
        fmt = "%(platform)s-%(distro)s-%(version)s-%(arch)s"
        print(fmt % {'platform' : platform, 'distro' : distro, 'version' : version, 'arch' :arch, })

    
if __name__ == "__main__":
    main(sys.argv[1:])
