import os, sys
from pypy.annotation import model as annmodel
from pypy.rpython.controllerentry import Controller
from pypy.rpython.extfunc import register_external
from pypy.rpython.lltypesystem import rffi, lltype
from pypy.rpython.module import ll_os
from pypy.rlib import rposix

str0 = ll_os.str0

# ____________________________________________________________
#
# Annotation support to control access to 'os.environ' in the RPython program

class OsEnvironController(Controller):
    knowntype = os.environ.__class__

    def convert(self, obj):
        return None     # 'None' is good enough, there is only one os.environ

    def getitem(self, obj, key):
        # in the RPython program reads of 'os.environ[key]' are redirected here
        result = r_getenv(key)
        if result is None:
            raise KeyError
        return result

    def setitem(self, obj, key, value):
        # in the RPython program, 'os.environ[key] = value' is redirected here
        r_putenv(key, value)

    def delitem(self, obj, key):
        # in the RPython program, 'del os.environ[key]' is redirected here
        if r_getenv(key) is None:
            raise KeyError
        r_unsetenv(key)

    def get_keys(self, obj):
        # 'os.environ.keys' is redirected here - note that it's the getattr
        # that arrives here, not the actual method call!
        return r_envkeys

    def get_items(self, obj):
        # 'os.environ.items' is redirected here (not the actual method call!)
        return r_envitems

    def get_get(self, obj):
        # 'os.environ.get' is redirected here (not the actual method call!)
        return r_getenv

# ____________________________________________________________
#
# Lower-level interface: dummy placeholders and external registations

def r_getenv(name):
    just_a_placeholder     # should return None if name not found

os_getenv = rffi.llexternal('getenv', [rffi.CCHARP], rffi.CCHARP)

def getenv_llimpl(name):
    l_name = rffi.str2charp(name)
    l_result = os_getenv(l_name)
    if l_result:
        result = rffi.charp2str(l_result)
    else:
        result = None
    rffi.free_charp(l_name)
    return result

register_external(r_getenv, [str0], annmodel.SomeString(can_be_None=True),
                  export_name='ll_os.ll_os_getenv',
                  llimpl=getenv_llimpl)

# ____________________________________________________________

def r_putenv(name, value):
    just_a_placeholder

class EnvKeepalive:
    pass
envkeepalive = EnvKeepalive()
envkeepalive.byname = {}

os_putenv = rffi.llexternal('putenv', [rffi.CCHARP], rffi.INT)

def putenv_llimpl(name, value):
    l_string = rffi.str2charp('%s=%s' % (name, value))
    error = rffi.cast(lltype.Signed, os_putenv(l_string))
    if error:
        rffi.free_charp(l_string)
        raise OSError(rposix.get_errno(), "os_putenv failed")
    # keep 'l_string' alive - we know that the C library needs it
    # until the next call to putenv() with the same 'name'.
    l_oldstring = envkeepalive.byname.get(name, lltype.nullptr(rffi.CCHARP.TO))
    envkeepalive.byname[name] = l_string
    if l_oldstring:
        rffi.free_charp(l_oldstring)

register_external(r_putenv, [str0, str0], annmodel.s_None,
                  export_name='ll_os.ll_os_putenv',
                  llimpl=putenv_llimpl)

# ____________________________________________________________

def r_unsetenv(name):
    # default implementation for platforms without a real unsetenv()
    r_putenv(name, '')

if hasattr(__import__(os.name), 'unsetenv'):

    if sys.platform.startswith('darwin'):
        RETTYPE = lltype.Void
        os_unsetenv = rffi.llexternal('unsetenv', [rffi.CCHARP], RETTYPE)
    else:
        RETTYPE = rffi.INT
        _os_unsetenv = rffi.llexternal('unsetenv', [rffi.CCHARP], RETTYPE)
        def os_unsetenv(l_name):
            return rffi.cast(lltype.Signed, _os_unsetenv(l_name))

    def unsetenv_llimpl(name):
        l_name = rffi.str2charp(name)
        error = os_unsetenv(l_name)     # 'error' is None on OS/X
        rffi.free_charp(l_name)
        if error:
            raise OSError(rposix.get_errno(), "os_unsetenv failed")
        try:
            l_oldstring = envkeepalive.byname[name]
        except KeyError:
            pass
        else:
            del envkeepalive.byname[name]
            rffi.free_charp(l_oldstring)

    register_external(r_unsetenv, [str0], annmodel.s_None,
                      export_name='ll_os.ll_os_unsetenv',
                      llimpl=unsetenv_llimpl)

# ____________________________________________________________
# Access to the 'environ' external variable

from pypy.translator.tool.cbuild import ExternalCompilationInfo

if sys.platform.startswith('darwin'):
    CCHARPPP = rffi.CArrayPtr(rffi.CCHARPP)
    _os_NSGetEnviron = rffi.llexternal(
        '_NSGetEnviron', [], CCHARPPP,
        compilation_info=ExternalCompilationInfo(includes=['crt_externs.h'])
        )
    def os_get_environ():
        return _os_NSGetEnviron()[0]
elif sys.platform.startswith('win'):
    os_get_environ, _os_set_environ = rffi.CExternVariable(
        rffi.CCHARPP,
        '_environ',
        ExternalCompilationInfo(includes=['stdlib.h']))
else:
    os_get_environ, _os_set_environ = rffi.CExternVariable(rffi.CCHARPP,
                                                           'environ',
                                                           ExternalCompilationInfo())

# ____________________________________________________________

def r_envkeys():
    just_a_placeholder

def envkeys_llimpl():
    environ = os_get_environ()
    result = []
    i = 0
    while environ[i]:
        name_value = rffi.charp2str(environ[i])
        p = name_value.find('=')
        if p >= 0:
            result.append(name_value[:p])
        i += 1
    return result

register_external(r_envkeys, [], [str0],   # returns a list of strings
                  export_name='ll_os.ll_os_envkeys',
                  llimpl=envkeys_llimpl)

# ____________________________________________________________

def r_envitems():
    just_a_placeholder

def envitems_llimpl():
    environ = os_get_environ()
    result = []
    i = 0
    while environ[i]:
        name_value = rffi.charp2str(environ[i])
        p = name_value.find('=')
        if p >= 0:
            result.append((name_value[:p], name_value[p+1:]))
        i += 1
    return result

register_external(r_envitems, [], [(str0, str0)],
                  export_name='ll_os.ll_os_envitems',
                  llimpl=envitems_llimpl)
