"""
The MIT License

Copyright (c) 2008 Vijay Ganesh
              2014 Jurriaan Bremer, jurriaanbremer@gmail.com
              2018-2021 Andrew V. Jones, andrewvaughanj@gmail.com

Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:

The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
"""

import ast
from ctypes import cdll, POINTER, CFUNCTYPE
from ctypes import c_char_p, c_void_p, c_int32, c_uint32, c_uint64, c_ulong, c_bool
import inspect
import os.path
import sys

__all__ = [
    'Expr', 'Solver', 'stp', 'add', 'bitvec', 'bitvecs', 'check', 'model',
    'get_git_version_sha', 'get_git_version_tag', 'get_compilation_env', 'get_lib'
]

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

if Py3:
    long = int

from library_path import PATHS

for path in PATHS:
    if not os.path.exists(path):
        continue

    _lib = cdll.LoadLibrary(path)
    break
else:
    raise Exception('Unable to locate the libstp shared object')


def _set_func(name, restype, *argtypes):
    getattr(_lib, name).restype = restype
    getattr(_lib, name).argtypes = argtypes

_VC = c_void_p
_Expr = c_void_p
_Type = c_void_p
_WholeCounterExample = c_void_p

_set_func('get_git_version_sha', c_char_p)
_set_func('get_git_version_tag', c_char_p)
_set_func('get_compilation_env', c_char_p)
_set_func('vc_createValidityChecker', _VC)
_set_func('vc_supportsMinisat', c_bool, _VC)
_set_func('vc_useMinisat', c_bool, _VC)
_set_func('vc_isUsingMinisat', c_bool, _VC)
_set_func('vc_supportsSimplifyingMinisat', c_bool, _VC)
_set_func('vc_useSimplifyingMinisat', c_bool, _VC)
_set_func('vc_isUsingSimplifyingMinisat', c_bool, _VC)
_set_func('vc_supportsCryptominisat', c_bool, _VC)
_set_func('vc_useCryptominisat', c_bool, _VC)
_set_func('vc_isUsingCryptominisat', c_bool, _VC)
_set_func('vc_supportsRiss', c_bool, _VC)
_set_func('vc_useRiss', c_bool, _VC)
_set_func('vc_isUsingRiss', c_bool, _VC)
_set_func('vc_boolType', _Type, _VC)
_set_func('vc_arrayType', _Type, _VC, _Type, _Type)
_set_func('vc_varExpr', _Expr, _VC, c_char_p, _Type)
_set_func('vc_varExpr1', _Expr, _VC, c_char_p, c_int32, c_int32)
_set_func('vc_getType', _Type, _VC, _Expr)
_set_func('vc_getBVLength', c_int32, _VC, _Expr)
_set_func('vc_eqExpr', _Expr, _VC, _Expr, _Expr)
_set_func('vc_trueExpr', _Expr, _VC)
_set_func('vc_falseExpr', _Expr, _VC)
_set_func('vc_notExpr', _Expr, _VC, _Expr)
_set_func('vc_andExpr', _Expr, _VC, _Expr, _Expr)
_set_func('vc_andExprN', _Expr, _VC, POINTER(_Expr), c_int32)
_set_func('vc_orExpr', _Expr, _VC, _Expr, _Expr)
_set_func('vc_xorExpr', _Expr, _VC, _Expr, _Expr)
_set_func('vc_orExprN', _Expr, _VC, POINTER(_Expr), c_int32)
_set_func('vc_impliesExpr', _Expr, _VC, _Expr, _Expr)
_set_func('vc_iffExpr', _Expr, _VC, _Expr, _Expr)
_set_func('vc_iteExpr', _Expr, _VC, _Expr, _Expr, _Expr)
_set_func('vc_boolToBVExpr', _Expr, _VC, _Expr)
_set_func('vc_paramBoolExpr', _Expr, _VC, _Expr, _Expr)
_set_func('vc_readExpr', _Expr, _VC, _Expr, _Expr)
_set_func('vc_writeExpr', _Expr, _VC, _Expr, _Expr, _Expr)
_set_func('vc_parseExpr', _Expr, _VC, c_char_p)
_set_func('vc_printExpr', None, _VC, _Expr)
_set_func('vc_printExprCCode', None, _VC, _Expr)
_set_func('vc_printSMTLIB', c_char_p, _VC, _Expr)
_set_func('vc_printExprFile', None, _VC, _Expr, c_int32)
_set_func('vc_printExprToBuffer', None, _VC, _Expr, POINTER(c_char_p), POINTER(c_ulong))
_set_func('vc_printCounterExample', None, _VC)
_set_func('vc_printVarDecls', None, _VC)
_set_func('vc_clearDecls', None, _VC)
_set_func('vc_printAsserts', None, _VC, c_int32)
_set_func('vc_printQueryStateToBuffer', None, _VC, _Expr, POINTER(c_char_p), POINTER(c_ulong), c_int32)
_set_func('vc_printCounterExampleToBuffer', None, _VC, POINTER(c_char_p), POINTER(c_ulong))
_set_func('vc_printQuery', None, _VC)
_set_func('vc_assertFormula', None, _VC, _Expr)
_set_func('vc_simplify', _Expr, _VC, _Expr)
_set_func('vc_query_with_timeout', c_int32, _VC, _Expr, c_int32, c_int32)
_set_func('vc_query', c_int32, _VC, _Expr)
_set_func('vc_getCounterExample', _Expr, _VC, _Expr)
_set_func('vc_getCounterExampleArray', None, _VC, _Expr, POINTER(POINTER(_Expr)), POINTER(POINTER(_Expr)), POINTER(c_int32))
_set_func('vc_counterexample_size', c_int32, _VC)
_set_func('vc_push', None, _VC)
_set_func('vc_pop', None, _VC)
_set_func('getBVInt', c_int32, _Expr)
_set_func('getBVUnsigned', c_uint32, _Expr)
_set_func('getBVUnsignedLongLong', c_uint64, _Expr)
_set_func('vc_bvType', _Type, _VC, c_int32)
_set_func('vc_bv32Type', _Type, _VC)
_set_func('vc_getValueSize', c_int32, _VC, _Type)
_set_func('vc_getIndexSize', c_int32, _VC, _Type)
_set_func('vc_bvConstExprFromDecStr', _Expr, _VC, c_int32, c_char_p)
_set_func('vc_bvConstExprFromStr', _Expr, _VC, c_char_p)
_set_func('vc_bvConstExprFromInt', _Expr, _VC, c_int32, c_uint32)
_set_func('vc_bvConstExprFromLL', _Expr, _VC, c_int32, c_uint64)
_set_func('vc_bv32ConstExprFromInt', _Expr, _VC, c_uint32)
_set_func('vc_bvConcatExpr', _Expr, _VC, _Expr, _Expr)
_set_func('vc_bvPlusExpr', _Expr, _VC, c_int32, _Expr, _Expr)
_set_func('vc_bvPlusExprN', _Expr, _VC, c_int32, POINTER(_Expr), c_int32)
_set_func('vc_bv32PlusExpr', _Expr, _VC, _Expr, _Expr)
_set_func('vc_bvMinusExpr', _Expr, _VC, c_int32, _Expr, _Expr)
_set_func('vc_bv32MinusExpr', _Expr, _VC, _Expr, _Expr)
_set_func('vc_bvMultExpr', _Expr, _VC, c_int32, _Expr, _Expr)
_set_func('vc_bv32MultExpr', _Expr, _VC, _Expr, _Expr)
_set_func('vc_bvDivExpr', _Expr, _VC, c_int32, _Expr, _Expr)
_set_func('vc_bvModExpr', _Expr, _VC, c_int32, _Expr, _Expr)
_set_func('vc_bvRemExpr', _Expr, _VC, c_int32, _Expr, _Expr)
_set_func('vc_sbvDivExpr', _Expr, _VC, c_int32, _Expr, _Expr)
_set_func('vc_sbvModExpr', _Expr, _VC, c_int32, _Expr, _Expr)
_set_func('vc_sbvRemExpr', _Expr, _VC, c_int32, _Expr, _Expr)
_set_func('vc_bvLtExpr', _Expr, _VC, _Expr, _Expr)
_set_func('vc_bvLeExpr', _Expr, _VC, _Expr, _Expr)
_set_func('vc_bvGtExpr', _Expr, _VC, _Expr, _Expr)
_set_func('vc_bvGeExpr', _Expr, _VC, _Expr, _Expr)
_set_func('vc_sbvLtExpr', _Expr, _VC, _Expr, _Expr)
_set_func('vc_sbvLeExpr', _Expr, _VC, _Expr, _Expr)
_set_func('vc_sbvGtExpr', _Expr, _VC, _Expr, _Expr)
_set_func('vc_sbvGeExpr', _Expr, _VC, _Expr, _Expr)
_set_func('vc_bvUMinusExpr', _Expr, _VC, _Expr)
_set_func('vc_bvAndExpr', _Expr, _VC, _Expr, _Expr)
_set_func('vc_bvOrExpr', _Expr, _VC, _Expr, _Expr)
_set_func('vc_bvXorExpr', _Expr, _VC, _Expr, _Expr)
_set_func('vc_bvNotExpr', _Expr, _VC, _Expr)
_set_func('vc_bvLeftShiftExprExpr', _Expr, _VC, c_int32, _Expr, _Expr)
_set_func('vc_bvRightShiftExprExpr', _Expr, _VC, c_int32,  _Expr, _Expr)
_set_func('vc_bvSignedRightShiftExprExpr', _Expr, _VC, c_int32, _Expr, _Expr)
_set_func('vc_bvLeftShiftExpr', _Expr, _VC, c_int32, _Expr)
_set_func('vc_bvRightShiftExpr', _Expr, _VC, c_int32, _Expr)
_set_func('vc_bv32LeftShiftExpr', _Expr, _VC, c_int32, _Expr)
_set_func('vc_bv32RightShiftExpr', _Expr, _VC, c_int32, _Expr)
_set_func('vc_bvVar32LeftShiftExpr', _Expr, _VC, _Expr, _Expr)
_set_func('vc_bvVar32RightShiftExpr', _Expr, _VC, _Expr, _Expr)
_set_func('vc_bvVar32DivByPowOfTwoExpr', _Expr, _VC, _Expr, _Expr)
_set_func('vc_bvExtract', _Expr, _VC, _Expr, c_int32, c_int32)
_set_func('vc_bvBoolExtract', _Expr, _VC, _Expr, c_int32)
_set_func('vc_bvBoolExtract_Zero', _Expr, _VC, _Expr, c_int32)
_set_func('vc_bvBoolExtract_One', _Expr, _VC, _Expr, c_int32)
_set_func('vc_bvSignExtend', _Expr, _VC, _Expr, c_int32)
_set_func('vc_bvCreateMemoryArray', _Expr, _VC, c_char_p)
_set_func('vc_bvReadMemoryArray', _Expr, _VC, _Expr, _Expr, c_int32)
_set_func('vc_bvWriteToMemoryArray', _Expr, _VC, _Expr, _Expr, _Expr, c_int32)
_set_func('vc_bv32ConstExprFromInt', _Expr, _VC, c_uint32)
_set_func('exprString', c_char_p, _Expr)
_set_func('typeString', c_char_p, _Type)
_set_func('getChild', _Expr, _Expr, c_int32)
_set_func('vc_isBool', c_int32, _Expr)
_set_func('vc_registerErrorHandler', None, CFUNCTYPE(None, c_char_p))
_set_func('vc_getHashQueryStateToBuffer', c_int32, _VC, _Expr)
_set_func('vc_Destroy', None, _VC)
_set_func('vc_DeleteExpr', None, _Expr)
_set_func('vc_getWholeCounterExample', _WholeCounterExample, _VC)
_set_func('vc_getTermFromCounterExample', _Expr, _VC, _Expr, _WholeCounterExample)
_set_func('vc_deleteWholeCounterExample', None, _WholeCounterExample)
_set_func('getDegree', c_int32, _Expr)
_set_func('getBVLength', c_int32, _Expr)
_set_func('getVWidth', c_int32, _Expr)
_set_func('getIWidth', c_int32, _Expr)
_set_func('vc_printCounterExampleFile', None, _VC, c_int32)
_set_func('exprName', c_char_p, _Expr)
_set_func('getExprID', c_int32, _Expr)
_set_func('vc_parseMemExpr', c_int32, _VC, c_char_p, POINTER(_Expr), POINTER(_Expr))


class Solver(object):
    current = None

    def __init__(self):
        self.keys = {}
        self.vc = _lib.vc_createValidityChecker()
        assert self.vc is not None, 'Error creating validity checker'

    def __del__(self):
        # TODO We're not quite there yet.
        # _lib.vc_Destroy(self.vc)
        pass

    def __enter__(self):
        Solver.current = self
        return self

    def __exit__(self, exc_type, exc_value, traceback):
        Solver.current = None

    def supportsMinisat(self):
        return _lib.vc_supportsMinisat(self.vc)

    def useMinisat(self):
        return _lib.vc_useMinisat(self.vc)

    def isUsingMinisat(self):
        return _lib.vc_isUsingMinisat(self.vc)

    def supportsSimplifyingMinisat(self):
        return _lib.vc_supportsSimplifyingMinisat(self.vc)

    def useSimplifyingMinisat(self):
        return _lib.vc_useSimplifyingMinisat(self.vc)

    def isUsingSimplifyingMinisat(self):
        return _lib.vc_isUsingSimplifyingMinisat(self.vc)

    def supportsCryptominisat(self):
        return _lib.vc_supportsCryptominisat(self.vc)

    def useCryptominisat(self):
        return _lib.vc_useCryptominisat(self.vc)

    def isUsingCryptominisat(self):
        return _lib.vc_isUsingCryptominisat(self.vc)

    def supportsRiss(self):
        return _lib.vc_supportsRiss(self.vc)

    def useRiss(self):
        return _lib.vc_useRiss(self.vc)

    def isUsingRiss(self):
        return _lib.vc_isUsingRiss(self.vc)

    def bitvec(self, name, width=32):
        """Creates a new BitVector variable."""
        # TODO Sanitize the name or stp will segfault.
        # TODO Perhaps cache these calls per width?
        # TODO Please, please, fix this terrible Py3 support.
        name_conv = bytes(name, 'utf8') if Py3 else name

        bv_type = _lib.vc_bvType(self.vc, width)
        self.keys[name] = _lib.vc_varExpr(self.vc, name_conv, bv_type)
        return Expr(self, width, self.keys[name], name=name)

    def bitvecs(self, names, width=32):
        """Creates one or more BitVectors variables."""
        return [self.bitvec(name, width) for name in names.split()]

    def bitvecval(self, width, value):
        """Creates a new BitVector with a constant value."""
        if value > 64: #32-bit?
            return self.bitvecvalD(width, str(value));
        else:
            expr = _lib.vc_bvConstExprFromLL(self.vc, width, value)
            return Expr(self, width, expr)

    def bitvecvalD(self, width, value):
        """Creates a new BitVector with a constant value."""
        value_conv = bytes(value, 'utf8') if Py3 else value
     
        expr = _lib.vc_bvConstExprFromDecStr(self.vc, width, value_conv)
        return Expr(self, width, expr)

    def true(self):
        """Creates a True boolean."""
        return Expr(self, None, _lib.vc_trueExpr(self.vc))

    def false(self):
        """Creates a False boolean."""
        return Expr(self, None, _lib.vc_falseExpr(self.vc))

    def add(self, *exprs):
        """Adds one or more constraint(s) to STP."""
        for expr in exprs:
            assert isinstance(expr, Expr), 'Formula should be an Expression'
            _lib.vc_assertFormula(self.vc, expr.expr)

    def push(self):
        """Enter a new frame."""
        _lib.vc_push(self.vc)

    def pop(self):
        """Leave the current frame."""
        _lib.vc_pop(self.vc)

    def _n_exprs(self, *exprs):
        """Creates an array of Expressions to be used in the C API."""
        for expr in exprs:
            assert isinstance(expr, Expr), 'Object should be an Expression'

        # This may not be very clean, but I'm not sure if there are
        # better ways to achieve this goal.
        exprs = [expr.expr for expr in exprs]
        exprs = (_Expr * len(exprs))(*exprs)
        return exprs, len(exprs)

    def check_with_timeout(self, *exprs, **kwargs):
        """Check whether the various expressions are satisfiable."""

        _, length = self._n_exprs(*exprs)
        if (length > 0):
            expr = self.and_(*exprs)
            expr = _lib.vc_notExpr(self.vc, expr.expr)
        else:
            expr = self.false().expr

        max_conflicts = kwargs.get("max_conflicts", -1)
        max_time = kwargs.get("max_time", -1)

        self.push()
        ret = _lib.vc_query_with_timeout(self.vc, expr, max_conflicts, max_time)
        self.pop()
        return ret

    def check(self, *exprs):
        ret = self.check_with_timeout(*exprs, max_conflicts=-1, max_time=-1)
        assert ret == 0 or ret == 1, 'Error querying your input'
        return not ret

    def model(self, key=None, expr=None):
        """
        Returns the value for an expression (or a name), or a model for the
        entire counterexample otherwise.

        Args:
            key (str): to look up a value by name
            expr (Expr): to look up a value by expression

        Returns:
            long (if expr or key is specified): the counterexample for the
                                                expression or key

            dict (if no expr or key): the counterexample model for all named
                                      keys
        """

        if expr and isinstance(expr, Expr):
            #
            # If we have an expression, and it is a _wrapped_ expression, we
            # need to unbox it
            #
            expr = expr.expr
        elif not expr and key is not None:
            #
            # If have no expression, but we have a key, then look-up based on
            # keys (values stored in `self.keys) is already unboxed
            #
            expr = self.keys[key]

        if expr:
            width = _lib.getVWidth(expr)
            
            #What if it's compiled for 32-bit?
            if width > 64:
                extract = 0
                for i in range(( width - 1 ) // 64 + 1):
                    extractExpr = _lib.vc_bvExtract(self.vc, expr, min ((i+1)*64, width)-1, i*64)
                    #maybe extractExpr leaks?
                    bvExpr = _lib.vc_getCounterExample(self.vc, extractExpr)
                    extract +=  (_lib.getBVUnsignedLongLong(bvExpr) << (64 * i))
                return extract
            else:
                value = _lib.vc_getCounterExample(self.vc, expr)
                return _lib.getBVUnsignedLongLong(value)
        #
        # If no expression, return a model giving values to simple bitvectors
        # (but not, e.g., arrays)
        #
        return dict((k, self.model(k)) for k in self.keys)

    # Allows easy access to the Counter Example.
    __getitem__ = model

    def and_(self, *exprs):
        exprs, length = self._n_exprs(*exprs)
        expr = _lib.vc_andExprN(self.vc, exprs, length)
        return Expr(self, None, expr)

    def or_(self, *exprs):
        exprs, length = self._n_exprs(*exprs)
        expr = _lib.vc_orExprN(self.vc, exprs, length)
        return Expr(self, None, expr)

    def xor(self, a, b):
        assert isinstance(a, Expr), 'Object must be an Expression'
        assert isinstance(b, Expr), 'Object must be an Expression'
        expr = _lib.vc_xorExpr(self.vc, a.expr, b.expr)
        return Expr(self, None, expr)
        
    def ite(self, a, b, c):
        assert isinstance(a, Expr), 'Object must be an Expression'
        assert isinstance(b, Expr), 'Object must be an Expression'
        assert isinstance(c, Expr), 'Object must be an Expression'
        assert b.width == c.width, 'Objects must have the same width' # compound expressions have width None or 0
        expr = _lib.vc_iteExpr(self.vc, a.expr, b.expr, c.expr)
        return Expr(self, b.width, expr)   
 
    def not_(self, obj):
        assert isinstance(obj, Expr), 'Object should be an Expression'
        expr = _lib.vc_notExpr(self.vc, obj.expr)
        return Expr(self, obj.width, expr)


class Expr(object):
    def __init__(self, s, width, expr, name=None):
        self.s = s
        self.width = width
        self.expr = expr
        self.name = name

    def __del__(self):
        # TODO We're not quite there yet.
        # _lib.vc_DeleteExpr(self.expr)
        pass

    def _1(self, cb):
        """Wrapper around single-expression STP functions."""
        expr = cb(self.s.vc, self.expr)
        return Expr(self.s, self.width, expr)

    def _1w(self, cb):
        """Wrapper around single-expression with width STP functions."""
        expr = cb(self.s.vc, self.width, self.expr)
        return Expr(self.s, self.width, expr)

    def _toexpr(self, other):
        if isinstance(other, (int, long)):
            return self.s.bitvecval(self.width, other)

        if isinstance(other, bool):
            return self.s.true() if other else self.s.false()

        return other

    def _2(self, cb, other):
        """Wrapper around double-expression STP functions."""
        other = self._toexpr(other)
        assert isinstance(other, Expr), 'Other object must be an Expr instance'
        expr = cb(self.s.vc, self.expr, other.expr)
        return Expr(self.s, self.width, expr)

    def _2w(self, cb, a, b):
        """Wrapper around double-expression with width STP functions."""
        a, b = self._toexpr(a), self._toexpr(b)
        assert isinstance(a, Expr), 'Left operand must be an Expr instance'
        assert isinstance(b, Expr), 'Right operand must be an Expr instance'
        assert self.width == a.width, 'Width must be equal'
        assert self.width == b.width, 'Width must be equal'
        expr = cb(self.s.vc, self.width, a.expr, b.expr)
        return Expr(self.s, self.width, expr)

    def add(self, other):
        return self._2w(_lib.vc_bvPlusExpr, self, other)

    __add__ = add
    __radd__ = add

    def sub(self, other):
        return self._2w(_lib.vc_bvMinusExpr, self, other)

    __sub__ = sub

    def rsub(self, other):
        return self._2w(_lib.vc_bvMinusExpr, other, self)

    __rsub__ = rsub

    def mul(self, other):
        return self._2w(_lib.vc_bvMultExpr, self, other)

    __mul__ = mul
    __rmul__ = mul

    def div(self, other):
        return self._2w(_lib.vc_bvDivExpr, self, other)

    __div__ = div
    __floordiv__ = div

    def rdiv(self, other):
        return self._2w(_lib.vc_bvDivExpr, other, self)

    __rdiv__ = rdiv
    __rfloordiv__ = rdiv

    def mod(self, other):
        return self._2w(_lib.vc_bvModExpr, self, other)

    __mod__ = mod

    def rmod(self, other):
        return self._2w(_lib.vc_bvModExpr, other, self)

    __rmod__ = rmod

    def rem(self, other):
        return self._2w(_lib.vc_bvRemExpr, self, other)

    def rrem(self, other):
        return self._2w(_lib.vc_bvRemExpr, other, self)

    def sdiv(self, other):
        return self._2w(_lib.vc_sbvDivExpr, self, other)

    def rsdiv(self, other):
        return self._2w(_lib.vc_sbvDivExpr, other, self)

    def smod(self, other):
        return self._2w(_lib.vc_sbvModExpr, self, other)

    def rsmod(self, other):
        return self._2w(_lib.vc_sbvModExpr, other, self)

    def srem(self, other):
        return self._2w(_lib.vc_sbvRemExpr, self, other)

    def rsrem(self, other):
        return self._2w(_lib.vc_sbvRemExpr, other, self)

    def eq(self, other):
        return self._2(_lib.vc_eqExpr, other)

    __eq__ = eq

    def ne(self, other):
        return self.s.not_(self.eq(other))

    __ne__ = ne

    def lt(self, other):
        return self._2(_lib.vc_bvLtExpr, other)

    __lt__ = lt

    def le(self, other):
        return self._2(_lib.vc_bvLeExpr, other)

    __le__ = le

    def gt(self, other):
        return self._2(_lib.vc_bvGtExpr, other)

    __gt__ = gt

    def ge(self, other):
        return self._2(_lib.vc_bvGeExpr, other)

    __ge__ = ge

    def slt(self, other):
        return self._2(_lib.vc_sbvLtExpr, other)

    def sle(self, other):
        return self._2(_lib.vc_sbvLeExpr, other)

    def sgt(self, other):
        return self._2(_lib.vc_sbvGtExpr, other)

    def sge(self, other):
        return self._2(_lib.vc_sbvGeExpr, other)

    def and_(self, other):
        return self._2(_lib.vc_bvAndExpr, other)

    __and__ = and_
    __rand__ = and_

    def or_(self, other):
        return self._2(_lib.vc_bvOrExpr, other)

    __or__ = or_
    __ror__ = or_

    def xor(self, other):
        return self._2(_lib.vc_bvXorExpr, other)

    __xor__ = xor
    __rxor__ = xor

    def neg(self):
        return self._1(_lib.vc_bvUMinusExpr)

    __neg__ = neg

    def __pos__(self):
        return self

    def not_(self):
        return self._1(_lib.vc_bvNotExpr)

    __invert__ = not_

    def shl(self, other):
        return self._2w(_lib.vc_bvLeftShiftExprExpr, self, other)

    __lshift__ = shl

    def rshl(self, other):
        return self._2w(_lib.vc_bvLeftShiftExprExpr, other, self)

    __rlshift__ = rshl

    def shr(self, other):
        return self._2w(_lib.vc_bvRightShiftExprExpr, self, other)

    __rshift__ = shr

    def rshr(self, other):
        return self._2w(_lib.vc_bvRightShiftExprExpr, other, self)

    __rrshift__ = rshr

    def sar(self, other):
        return self._2w(_lib.vc_bvSignedRightShiftExprExpr, self, other)

    def rsar(self, other):
        return self._2w(_lib.vc_bvSignedRightShiftExprExpr, other, self)

    def extract(self, high, low):
        expr = _lib.vc_bvExtract(self.s.vc, self.expr, high, low)
        return Expr(self.s, self.width, expr)

    def simplify(self):
        """Simplify an expression."""
        expr = _lib.vc_simplify(self.s.vc, self.expr)
        return Expr(self.s, self.width, expr)

    @property
    def value(self):
        """Returns the value of this BitVec in the current model."""
        return self.s.model(expr=self)


class ASTtoSTP(ast.NodeVisitor):
    def __init__(self, s, count, *args, **kwargs):
        ast.NodeVisitor.__init__(self)
        self.s = s
        self.count = count
        self.inside = False
        self.func_name = None
        self.bitvecs = {}
        self.exprs = []
        self.returned = None
        self.args = args
        self.kwargs = kwargs

    def _super(self, node):
        return super(ASTtoSTP, self).generic_visit(node)

    visit_Module = _super

    def visit_FunctionDef(self, node):
        assert node.args.vararg is None and node.args.kwarg is None, \
            'Variable and Keyword arguments are not allowed'

        if self.inside:
            raise Exception('Nested functions are not allowed')

        self.inside = True
        self.func_name = node.name

        for idx, arg in enumerate(node.args.args):
            arg = arg.arg if Py3 else arg.id
            name = '%s_%d_%s' % (self.func_name, self.count, arg)
            if idx < len(self.args):
                self.bitvecs[name] = self.args[idx]
                continue

            if arg in self.kwargs:
                self.bitvecs[name] = self.kwargs[arg]
                continue

            width = 32
            if idx < len(node.args.defaults):
                width = node.args.defaults[idx]

            self.bitvecs[name] = self.s.bitvec(name, width=width)

        for row in node.body:
            self.visit(row)

    def visit_Num(self, node):
        return node.n

    def visit_BoolOp(self, node):
        ops = {
            ast.And: self.s.and_,
            ast.Or: self.s.or_,
        }
        x = self.visit(node.values[0])
        y = self.visit(node.values[1])
        return ops[node.op.__class__](x, y)

    def visit_BinOp(self, node):
        ops = {
            ast.Add: lambda x, y: x + y,
            ast.Sub: lambda x, y: x - y,
            ast.Mult: lambda x, y: x * y,
            ast.Div: lambda x, y: x / y,
            ast.Mod: lambda x, y: x % y,
            ast.LShift: lambda x, y: x << y,
            ast.RShift: lambda x, y: x >> y,
            ast.BitOr: lambda x, y: x | y,
            ast.BitXor: lambda x, y: x ^ y,
            ast.BitAnd: lambda x, y: x & y,
        }
        x = self.visit(node.left)
        y = self.visit(node.right)
        return ops[node.op.__class__](x, y)

    def visit_Compare(self, node):
        assert len(node.ops) == 1, 'TODO Support multiple comparison ops'

        cmps = {
            ast.Eq: lambda x, y: x == y,
            ast.NotEq: lambda x, y: x != y,
            ast.Lt: lambda x, y: x < y,
            ast.LtE: lambda x, y: x <= y,
            ast.Gt: lambda x, y: x > y,
            ast.GtE: lambda x, y: x >= y,
            ast.Is: lambda x, y: x == y,
            ast.IsNot: lambda x, y: x != y,
        }

        x = self.visit(node.left)
        y = self.visit(node.comparators[0])
        return cmps[node.ops[0].__class__](x, y)

    def visit_Name(self, node):
        if isinstance(node.ctx, ast.Load):
            name = '%s_%d_%s' % (self.func_name, self.count, node.id)
            return self.bitvecs[name]

        raise

    def visit_Assert(self, node):
        self.exprs.append(self.visit(node.test))

    def visit_Return(self, node):
        self.returned = self.visit(node.value)

    def generic_visit(self, node):
        raise Exception(node.__class__.__name__ + ' is not yet supported!')


def _eval_ast(root, *args, **kwargs):
    s = Solver.current
    node = ASTtoSTP(s, root.count-1, *args, **kwargs)
    node.visit(root)
    if node.exprs:
        s.add(*node.exprs)
    return node.returned


def stp(f):
    try:
        src = inspect.getsource(f)
    except IOError:
        raise Exception(
            'It is only possible to use the @stp decorator when the '
            'function is stored in a source file. It does *not* work '
            'directly from the Python interpreter.')

    node = ast.parse(src)
    node.count = 0

    def h(*args, **kwargs):
        node.count += 1
        return _eval_ast(node, *args, **kwargs)

    return h


def add(*args, **kwargs):
    return Solver.current.add(*args, **kwargs)


def bitvec(*args, **kwargs):
    return Solver.current.bitvec(*args, **kwargs)


def bitvecs(*args, **kwargs):
    return Solver.current.bitvecs(*args, **kwargs)


def check(*args, **kwargs):
    return Solver.current.check(*args, **kwargs)


def model(*args, **kwargs):
    return Solver.current.model(*args, **kwargs)

def get_git_version_sha():
    return _lib.get_git_version_sha()

def get_git_version_tag():
    return _lib.get_git_version_tag()

def get_compilation_env():
    return _lib.get_compilation_env()

def get_lib():
    #
    # Helper function to expose STP's CDLL object -- this allows for API-based
    # access to STP's API without going via the `Solver` class.
    #
    # There are some calls (e.g., vc_bvConcatExpr) that exist inside of the
    # `_lib` object, but which do not exist inside of `Solver`.
    #
    # Directly exposing `_lib_ allows for access to these routines in the API.
    #
    return _lib

# EOF
