import os
import shutil
import subprocess
import sys
import pwd
import grp
import re

class BuildUtil(object):

    def __init__(self):
        self.log = False
        self.name = False
        self.num_errors = 0
        self.num_warnings = 0

    def run(self, cmd, stdout=False, stderr=False, verbose=True):
        if isinstance(cmd, basestring):
            if stdout and stderr:
                cmd += ' 1>%s 2>%s' % (stdout, stderr)
            elif stdout and not stderr:
                cmd += ' 1>%s 2>&1' % (stdout)
            elif not stdout and stderr:
                cmd += ' 2>%s' % (stderr)
            if verbose: self.log_info(cmd)
            os.system(cmd)
        else:
            str = ' '.join(cmd);
            if stdout:
                f = file(stdout, 'w+')
                str += ' 1>%s 2>&1' % (stdout)
            else:
                f = subprocess.PIPE
            if verbose: self.log_info(str)
            try:
                sp = subprocess.Popen(cmd, stdout=f, stderr=subprocess.PIPE)
            except OSError as e:
                self.log_err(cmd)
                self.log_err(e)
                return 0,e.message#raise
            if stdout: f.close()
            out, err = sp.communicate()
            # print 'run returned %d bytes stdout and %d bytes stderr' % (len(out) if out else 0, len(err) if err else 0)
            return out, err

    def isdir(self, dname):
        return os.path.isdir(dname)

    def getcwd(self):
        return os.getcwd()

    def basename(self, fname):
        return os.path.basename(fname)

    def dirname(self, fname):
        return os.path.dirname(fname)

    def getmtime(self, fname):
        return os.path.getmtime(fname)

    def isfile(self, fname):
        return os.path.isfile(fname)

    def getenv(self, var, dflt):
        return os.getenv(var, dflt)

    def pw_name(self, fname):
        return pwd.getpwuid(os.stat(fname).st_uid).pw_name

    def gr_name(self, fname):
        return grp.getgrgid(os.stat(fname).st_gid).gr_name

    def isatty(self):
        return os.isatty(sys.stdin.fileno())

    def is_readable_file(self, fname):
        if not self.isfile(fname):
            self.log_warn('%s does not exist' % fname)
            return False
        if os.stat(fname).st_size == 0:
            self.log_warn('%s is empty' % fname)
            return False
        if os.access(fname, os.R_OK) == 0:
            self.log_warn('%s is not readable' % fname)
            return False
        return True

    def is_executable_file(self, fname):
        if not self.is_readable_file(fname): return False
        if os.access(fname, os.X_OK) == 0:
            self.log_warn('%s is not executable' % fname)
            return False
        return True

    # export a file to a directory
    def export_file(self, fname, dname,):
        try:
            if not os.path.isdir(dname):
                self.makedirs(dname)
            if os.path.isfile(fname):
                self.copy(fname, dname, verbose=True)
            elif os.path.isdir(fname):
                self.copy(fname, dname, dir=True, verbose=True)
        except IOError as e:
            self.log_err('Error occurred exporting %s to %s' % (fname, dname))
            self.log_err("Unexpected error: %s" % str(e))

    def rmtree(self, dir, verbose=True):
        if verbose: self.log_info('rm -r %s' % dir)
        shutil.rmtree(dir)

    def makedirs(self, dir, verbose=True):
        if verbose: self.log_info('mkdir -p %s' % dir)
        try: os.makedirs(dir)
        except: pass

    # copy a file to a directory
    def copy(self, fname, dname, verbose=True, dir=False):
        if not dir:
            if verbose: self.log_info('cp -av %s %s' % (fname, dname))
            shutil.copy(fname, dname)
        else:
            if verbose: self.log_info('cp -avr %s %s' % (fname, dname))
            if os.path.exists(dname):
                shutil.rmtree(dname)
            shutil.copytree(fname, dname)

    def chdir(self, dir, verbose=True):
        if verbose: self.log_info('cd %s' % dir)
        os.chdir(dir)

    def remove(self, fname, verbose=True):
        if verbose: self.log_info('rm -f %s' % fname)
        try: os.remove(fname)
        except: pass

    def environment(self, var, val, verbose=True):
        if verbose: self.log_info('%s=%s' % (var, val))
        os.environ[var] = val

    def unlink(self, targ, verbose=True):
        if verbose: self.log_info('unlink %s' % targ)
        os.unlink(targ)

    def symlink(self, src, targ, verbose=True):
        if verbose: self.log_info('ln -s %s %s' % (src, targ))
        if os.path.islink(targ):
            os.unlink(targ)
        os.symlink(src, targ)

    def build_dir(self, root, kind, what):
        return root + "/" + re.sub(r'[^a-zA-Z0-9_-]+', '_', 'build-%s-%s' % (kind, what))

    def log_prefix(self, kind, what):
        return kind.upper() + ' ' + what

    def open_log(self, root, kind, what, chdir=False):
        build_dir = self.build_dir(root, kind, what)

        # Get the name of the log file
        logFile = '%s/log.txt' % build_dir

        self.log_info('%s LOGFILE %s' % (self.log_prefix(kind, what), logFile))

        try: self.rmtree(build_dir, verbose=False)
        except: pass
        self.makedirs(build_dir, verbose=False)
        self.log_open(logFile)
        if chdir: self.chdir(build_dir)

    def log_open(self, name):
        if self.log: self.log_close()
        self.log = open(name, 'w')
        self.name = name

    def log_close(self):
        if self.log:
            if self.num_errors > 0:
                print '# ERROR: There were errors, see %s' % self.name
            elif self.num_warnings > 0:
                print '# WARNING: There were warnings, see %s' % self.name
            self.log.close()
        self.log = False
        self.name = False
        self.num_errors = 0
        self.num_warnings = 0

    def log_pr(self, what):
        if self.log:
            self.log.write(what + '\n')
            self.log.flush()
        else:
            print what
            sys.stdout.flush()

    def log_err(self, what):
        self.log_pr('# ERROR: ' + what)
        self.num_errors += 1

    def log_warn(self, what):
        self.log_pr('# WARNING: ' + what)
        self.num_warnings += 1

    def log_info(self, what):
        self.log_pr('# INFO: ' + what)

    # create a file with size, type, and symbol info
    # the function is here because it is useful and has no dependencies

    def mkinfo(self, fname):
        ifdefs = { 'i8':'HAS_LONGLONG', 'u8':'HAS_LONGLONG', 'f4':'HAS_FLOAT', 'f8':'HAS_DOUBLE' }

        sizes = [
            'char', 'signed char', 'unsigned char',
            'short', 'signed short', 'unsigned short',
            'int', 'signed int', 'unsigned int',
            'long', 'signed long', 'unsigned long',
            'long long', 'signed long long', 'unsigned long long',
            'float', 'double', 'float', 'long double',
            'i1', 'i2', 'i4', 'u1', 'u2', 'u4', 'i8', 'u8', 'f4', 'f8']

        syms = [
            '__AVR32__', '__AVR_ARCH__', 'dsPIC30', '__GNUC__', '__has_feature', 'INT4_IS_LONG',
            '__INT64_TYPE__', '__INT8_TYPE__', '__llvm__', '_M_ARM_FP', '__MSP430__', '_MSV_VER',
            '__SDCC', '__SIZEOF_DOUBLE__', '__SIZEOF_FLOAT__', '__SIZEOF_SIZE_T__', '__TI_COMPILER_VERSION__',
            '__INT8_TYPE__', '__INT16_TYPE__', '__INT32_TYPE__', '__INT64_TYPE__', '__UINT8_TYPE__',
            '__UINT16_TYPE__', '__UINT32_TYPE__', '__UINT64_TYPE__', 'HAS_FLOAT', 'HAS_DOUBLE',
            'HAS_LONGLONG', 'HAS_FLOAT_OVERRIDE', 'HAS_DOUBLE_OVERRIDE', 'HAS_LONGLONG_OVERRIDE']

        typedefs = { 'i1':1, 'i2':2, 'i4':4, 'u1':1, 'u2':2, 'u4':4, 'i8':8, 'u8':8, 'f4':4, 'f8':8 }

        f = open(fname, 'w')

        f.write('#include "types.h"\n\n')

        i = 0
        for s in sizes:
            i += 1
            d = 'INFO sizeof(%s) = ' % s
            x = list(d)
            x = "', '".join(x)
            x = "'%s', '0'+sizeof(%s), '\\n'" % (x, s)
            l = 'char size_info_%d[] = {%s};\n' % (i, x)
            if s in ifdefs: f.write('#ifdef %s\n' % ifdefs[s])
            f.write(l)
            if s in ifdefs: f.write('#endif\n')

        for s in typedefs:
            if s in ifdefs: f.write('#ifdef %s\n' % ifdefs[s])
            f.write('_Static_assert(sizeof(%s) == %d, "INFO %s should have size %d, is not correct\\n");\n' % (s, typedefs[s], s, typedefs[s]))
            if s in ifdefs: f.write('#endif\n')

        for s in syms:
            i += 1
            f.write('#ifdef %s\n' % s)
            f.write('char sym_info_%d[] = "INFO %s is defined\\n\";\n' % (i, s))
            f.write('#else\n')
            f.write('char sym_info_%d[] = "INFO %s is not defined\\n\";\n' % (i, s))
            f.write('#endif\n')

        f.close()

class Config(object):

    def __init__(self, *obj):
        for o in obj:
            if isinstance(o, dict): self.__dict__.update(o)
            else: self.__dict__.update(o.__dict__)

    def format(self, val):
        if isinstance(val, basestring) and '%' in val:
            return val % self.__dict__
        elif isinstance(val, dict):
            return dict(map(lambda (k,v): (k,self.format(v)), val.iteritems()))
        else: return val

    def __getattr__(self, attr):
        return ''

    def expand(self):
        for k,v in self.__dict__.iteritems():
            self.__dict__[k] = self.format(v)

    def dump(self):
        ret = ''
        for k,v in sorted(self.__dict__.iteritems()):
            if isinstance(v, basestring): vv = "'" + v + "'"
            else: vv = str(v)
            ret += ' '.ljust(10) + k.ljust(20) + vv + '\n'
        return ret

