/* Copyright 2009-2015 EPFL, Lausanne */

package leon
package solvers.z3

import leon.utils._

import z3.scala._
import solvers._
import purescala.Common._
import purescala.Definitions._
import purescala.Constructors._
import purescala.Extractors._
import purescala.Expressions._
import purescala.TypeOps._
import xlang.Expressions._
import purescala.ExprOps._
import purescala.Types._

import scala.collection.mutable.{Map => MutableMap}

case class UnsoundExtractionException(ast: Z3AST, msg: String)
  extends Exception("Can't extract " + ast + " : " + msg)

// This is just to factor out the things that are common in "classes that deal
// with a Z3 instance"
trait AbstractZ3Solver extends Solver {

  val program : Program

  val library = program.library

  protected[z3] val reporter : Reporter = context.reporter

  context.interruptManager.registerForInterrupts(this)

  private[this] var freed = false
  val traceE = new Exception()

  protected def unsound(ast: Z3AST, msg: String): Nothing =
    throw UnsoundExtractionException(ast, msg)

  override def finalize() {
    if (!freed) {
      println("!! Solver "+this.getClass.getName+"["+this.hashCode+"] not freed properly prior to GC:")
      traceE.printStackTrace()
      free()
    }
  }

  protected[leon] val z3cfg : Z3Config
  protected[leon] var z3 : Z3Context    = null

  override def free() {
    freed = true
    if (z3 ne null) {
      z3.delete()
      z3 = null
    }
  }

  protected[z3] var interrupted = false

  override def interrupt() {
    interrupted = true
    if(z3 ne null) {
      z3.interrupt()
    }
  }

  override def recoverInterrupt() {
    interrupted = false
  }

  def functionDefToDecl(tfd: TypedFunDef): Z3FuncDecl = {
    functions.cachedB(tfd) {
      val sortSeq    = tfd.params.map(vd => typeToSort(vd.getType))
      val returnSort = typeToSort(tfd.returnType)

      z3.mkFreshFuncDecl(tfd.id.uniqueName, sortSeq, returnSort)
    }
  }

  def genericValueToDecl(gv: GenericValue): Z3FuncDecl = {
    generics.cachedB(gv) {
      z3.mkFreshFuncDecl(gv.tp.id.uniqueName+"#"+gv.id+"!val", Seq(), typeToSort(gv.tp))
    }
  }

  // ADT Manager
  protected val adtManager = new ADTManager(context)

  // Bijections between Leon Types/Functions/Ids to Z3 Sorts/Decls/ASTs
  protected val functions = new IncrementalBijection[TypedFunDef, Z3FuncDecl]()
  protected val generics  = new IncrementalBijection[GenericValue, Z3FuncDecl]()
  protected val lambdas   = new IncrementalBijection[FunctionType, Z3FuncDecl]()
  protected val sorts     = new IncrementalBijection[TypeTree, Z3Sort]()
  protected val variables = new IncrementalBijection[Expr, Z3AST]()

  protected val constructors  = new IncrementalBijection[TypeTree, Z3FuncDecl]()
  protected val selectors     = new IncrementalBijection[(TypeTree, Int), Z3FuncDecl]()
  protected val testers       = new IncrementalBijection[TypeTree, Z3FuncDecl]()

  var isInitialized = false
  protected[leon] def initZ3() {
    if (!isInitialized) {
      val timer = context.timers.solvers.z3.init.start()

      z3 = new Z3Context(z3cfg)

      functions.clear()
      lambdas.clear()
      generics.clear()
      sorts.clear()
      variables.clear()
      constructors.clear()
      selectors.clear()
      testers.clear()

      prepareSorts()

      isInitialized = true

      timer.stop()
    }
  }

  protected[leon] def restartZ3() {
    isInitialized = false

    initZ3()
  }

  def rootType(ct: TypeTree): TypeTree = ct match {
    case ct: ClassType => ct.root
    case t => t
  }

  def declareStructuralSort(t: TypeTree): Z3Sort = {
    //println("///"*40)
    //println("Declaring for: "+t)

    adtManager.defineADT(t) match {
      case Left(adts) =>
        declareDatatypes(adts.toSeq)
        sorts.toB(normalizeType(t))

      case Right(conflicts) =>
        conflicts.foreach { declareStructuralSort }
        declareStructuralSort(t)
    }
  }

  def declareDatatypes(adts: Seq[(TypeTree, DataType)]): Unit = {
    import Z3Context.{ADTSortReference, RecursiveType, RegularSort}

    val indexMap: Map[TypeTree, Int] = adts.map(_._1).zipWithIndex.toMap

    def typeToSortRef(tt: TypeTree): ADTSortReference = {
      val tpe = rootType(tt)

      if (indexMap contains tpe) {
        RecursiveType(indexMap(tpe))
      } else {
        RegularSort(typeToSort(tt))
      }
    }

    // Define stuff
    val defs = for ((_, DataType(sym, cases)) <- adts) yield {(
      sym.uniqueName,
      cases.map(c => c.sym.uniqueName),
      cases.map(c => c.fields.map{ case(id, tpe) => (id.uniqueName, typeToSortRef(tpe))})
    )}

    val resultingZ3Info = z3.mkADTSorts(defs)

    for ((z3Inf, (tpe, DataType(sym, cases))) <- resultingZ3Info zip adts) {
      sorts += (tpe -> z3Inf._1)
      assert(cases.size == z3Inf._2.size)

      for ((c, (consFun, testFun)) <- cases zip (z3Inf._2 zip z3Inf._3)) {
        testers += (c.tpe -> testFun)
        constructors += (c.tpe -> consFun)
      }

      for ((c, fieldFuns) <- cases zip z3Inf._4) {
        assert(c.fields.size == fieldFuns.size)

        for ((selFun, index) <- fieldFuns.zipWithIndex) {
          selectors += (c.tpe, index) -> selFun
        }
      }
    }
  }

  // Prepares some of the Z3 sorts, but *not* the tuple sorts; these are created on-demand.
  private def prepareSorts(): Unit = {

    z3.mkADTSorts(
      Seq((
        "Unit",
        Seq("Unit"),
        Seq(Seq())
      ))
    )

    //TODO: mkBitVectorType
    sorts += Int32Type -> z3.mkBVSort(32)
    sorts += CharType -> z3.mkBVSort(32)
    sorts += IntegerType -> z3.mkIntSort
    sorts += RealType -> z3.mkRealSort
    sorts += BooleanType -> z3.mkBoolSort

    testers.clear()
    constructors.clear()
    selectors.clear()
  }

  def normalizeType(t: TypeTree): TypeTree = {
    bestRealType(t)
  }

  // assumes prepareSorts has been called....
  protected[leon] def typeToSort(oldtt: TypeTree): Z3Sort = normalizeType(oldtt) match {
    case Int32Type | BooleanType | IntegerType | RealType | CharType =>
      sorts.toB(oldtt)

    case tpe @ (_: ClassType  | _: ArrayType | _: TupleType | UnitType) =>
      sorts.cachedB(tpe) {
        declareStructuralSort(tpe)
      }

    case tt @ SetType(base) =>
      sorts.cachedB(tt) {
        z3.mkSetSort(typeToSort(base))
      }

    case tt @ MapType(fromType, toType) =>
      typeToSort(RawArrayType(fromType, library.optionType(toType)))

    case rat @ RawArrayType(from, to) =>
      sorts.cachedB(rat) {
        val fromSort = typeToSort(from)
        val toSort = typeToSort(to)

        z3.mkArraySort(fromSort, toSort)
      }

    case tt @ TypeParameter(id) =>
      sorts.cachedB(tt) {
        val symbol = z3.mkFreshStringSymbol(id.name)
        val newTPSort = z3.mkUninterpretedSort(symbol)

        newTPSort
      }

    case ft @ FunctionType(from, to) =>
      sorts.cachedB(ft) {
        val symbol = z3.mkFreshStringSymbol(ft.toString)
        z3.mkUninterpretedSort(symbol)
      }

    case other =>
      throw SolverUnsupportedError(other, this)
  }

  protected[leon] def toZ3Formula(expr: Expr, initialMap: Map[Identifier, Z3AST] = Map.empty): Z3AST = {

    var z3Vars: Map[Identifier,Z3AST] = if(initialMap.nonEmpty) {
      initialMap
    } else {
      // FIXME TODO pleeeeeeeease make this cleaner. Ie. decide what set of
      // variable has to remain in a map etc.
      variables.aToB.collect{ case (Variable(id), p2) => id -> p2 }
    }

    def rec(ex: Expr): Z3AST = ex match {

      // TODO: Leave that as a specialization?
      case LetTuple(ids, e, b) => {
        z3Vars = z3Vars ++ ids.zipWithIndex.map { case (id, ix) =>
          val entry = id -> rec(tupleSelect(e, ix + 1, ids.size))
          entry
        }
        val rb = rec(b)
        z3Vars = z3Vars -- ids
        rb
      }

      case p @ Passes(_, _, _) =>
        rec(p.asConstraint)

      case me @ MatchExpr(s, cs) =>
        rec(matchToIfThenElse(me))

      case Let(i, e, b) => {
        val re = rec(e)
        z3Vars = z3Vars + (i -> re)
        val rb = rec(b)
        z3Vars = z3Vars - i
        rb
      }

      case Waypoint(_, e, _) => rec(e)
      case a @ Assert(cond, err, body) =>
        rec(IfExpr(cond, body, Error(a.getType, err.getOrElse("Assertion failed")).setPos(a.getPos)).setPos(a.getPos))

      case e @ Error(tpe, _) => {
        val newAST = z3.mkFreshConst("errorValue", typeToSort(tpe))
        // Might introduce dupplicates (e), but no worries here
        variables += (e -> newAST)
        newAST
      }
      case v @ Variable(id) => z3Vars.get(id) match {
        case Some(ast) => 
          ast
        case None => {
          variables.getB(v) match {
            case Some(ast) =>
              ast

            case None =>
          val newAST = z3.mkFreshConst(id.uniqueName, typeToSort(v.getType))
          z3Vars = z3Vars + (id -> newAST)
          variables += (v -> newAST)
          newAST
        }
      }
      }

      case ite @ IfExpr(c, t, e) => z3.mkITE(rec(c), rec(t), rec(e))
      case And(exs) => z3.mkAnd(exs.map(rec): _*)
      case Or(exs) => z3.mkOr(exs.map(rec): _*)
      case Implies(l, r) => z3.mkImplies(rec(l), rec(r))
      case Not(Equals(l, r)) => z3.mkDistinct(rec(l), rec(r))
      case Not(e) => z3.mkNot(rec(e))
      case IntLiteral(v) => z3.mkInt(v, typeToSort(Int32Type))
      case InfiniteIntegerLiteral(v) => z3.mkNumeral(v.toString, typeToSort(IntegerType))
      case FractionalLiteral(n, d) => z3.mkNumeral(s"$n / $d", typeToSort(RealType))
      case CharLiteral(c) => z3.mkInt(c, typeToSort(CharType))
      case BooleanLiteral(v) => if (v) z3.mkTrue() else z3.mkFalse()
      case Equals(l, r) => z3.mkEq(rec( l ), rec( r ) )
      case Plus(l, r) => z3.mkAdd(rec(l), rec(r))
      case Minus(l, r) => z3.mkSub(rec(l), rec(r))
      case Times(l, r) => z3.mkMul(rec(l), rec(r))
      case Division(l, r) => {
        val rl = rec(l)
        val rr = rec(r)
        z3.mkITE(
          z3.mkGE(rl, z3.mkNumeral("0", typeToSort(IntegerType))),
          z3.mkDiv(rl, rr),
          z3.mkUnaryMinus(z3.mkDiv(z3.mkUnaryMinus(rl), rr))
        )
      }
      case Remainder(l, r) => {
        val q = rec(Division(l, r))
        z3.mkSub(rec(l), z3.mkMul(rec(r), q))
      }
      case Modulo(l, r) => {
        z3.mkMod(rec(l), rec(r))
      }
      case UMinus(e) => z3.mkUnaryMinus(rec(e))

      case RealPlus(l, r) => z3.mkAdd(rec(l), rec(r))
      case RealMinus(l, r) => z3.mkSub(rec(l), rec(r))
      case RealTimes(l, r) => z3.mkMul(rec(l), rec(r))
      case RealDivision(l, r) => z3.mkDiv(rec(l), rec(r))
      case RealUMinus(e) => z3.mkUnaryMinus(rec(e))

      case BVPlus(l, r) => z3.mkBVAdd(rec(l), rec(r))
      case BVMinus(l, r) => z3.mkBVSub(rec(l), rec(r))
      case BVTimes(l, r) => z3.mkBVMul(rec(l), rec(r))
      case BVDivision(l, r) => z3.mkBVSdiv(rec(l), rec(r))
      case BVRemainder(l, r) => z3.mkBVSrem(rec(l), rec(r))
      case BVUMinus(e) => z3.mkBVNeg(rec(e))
      case BVNot(e) => z3.mkBVNot(rec(e))
      case BVAnd(l, r) => z3.mkBVAnd(rec(l), rec(r))
      case BVOr(l, r) => z3.mkBVOr(rec(l), rec(r))
      case BVXOr(l, r) => z3.mkBVXor(rec(l), rec(r))
      case BVShiftLeft(l, r) => z3.mkBVShl(rec(l), rec(r))
      case BVAShiftRight(l, r) => z3.mkBVAshr(rec(l), rec(r))
      case BVLShiftRight(l, r) => z3.mkBVLshr(rec(l), rec(r))
      case LessThan(l, r) => l.getType match {
        case IntegerType => z3.mkLT(rec(l), rec(r))
        case RealType => z3.mkLT(rec(l), rec(r))
        case Int32Type => z3.mkBVSlt(rec(l), rec(r))
        case CharType => z3.mkBVSlt(rec(l), rec(r))
      }
      case LessEquals(l, r) => l.getType match {
        case IntegerType => z3.mkLE(rec(l), rec(r))
        case RealType => z3.mkLE(rec(l), rec(r))
        case Int32Type => z3.mkBVSle(rec(l), rec(r))
        case CharType => z3.mkBVSle(rec(l), rec(r))
        //case _ => throw new IllegalStateException(s"l: $l, Left type: ${l.getType} Expr: $ex")
      }
      case GreaterThan(l, r) => l.getType match {
        case IntegerType => z3.mkGT(rec(l), rec(r))
        case RealType => z3.mkGT(rec(l), rec(r))
        case Int32Type => z3.mkBVSgt(rec(l), rec(r))
        case CharType => z3.mkBVSgt(rec(l), rec(r))
      }
      case GreaterEquals(l, r) => l.getType match {
        case IntegerType => z3.mkGE(rec(l), rec(r))
        case RealType => z3.mkGE(rec(l), rec(r))
        case Int32Type => z3.mkBVSge(rec(l), rec(r))
        case CharType => z3.mkBVSge(rec(l), rec(r))
      }

      case u : UnitLiteral =>
        val tpe = normalizeType(u.getType)
        typeToSort(tpe)
        val constructor = constructors.toB(tpe)
        constructor()

      case t @ Tuple(es) =>
        val tpe = normalizeType(t.getType)
        typeToSort(tpe)
        val constructor = constructors.toB(tpe)
        constructor(es.map(rec): _*)

      case ts @ TupleSelect(t, i) =>
        val tpe = normalizeType(t.getType)
        typeToSort(tpe)
        val selector = selectors.toB((tpe, i-1))
        selector(rec(t))

      case c @ CaseClass(ct, args) =>
        typeToSort(ct) // Making sure the sort is defined
        val constructor = constructors.toB(ct)
        constructor(args.map(rec): _*)

      case c @ CaseClassSelector(cct, cc, sel) =>
        typeToSort(cct) // Making sure the sort is defined
        val selector = selectors.toB(cct, c.selectorIndex)
        selector(rec(cc))

      case AsInstanceOf(expr, ct) =>
        rec(expr)

      case IsInstanceOf(e, act: AbstractClassType) =>
        act.knownCCDescendants match {
          case Seq(cct) =>
            rec(IsInstanceOf(e, cct))
          case more =>
            val i = FreshIdentifier("e", act, alwaysShowUniqueID = true)
            rec(Let(i, e, orJoin(more map(IsInstanceOf(Variable(i), _)))))
        }

      case IsInstanceOf(e, cct: CaseClassType) =>
        typeToSort(cct) // Making sure the sort is defined
        val tester = testers.toB(cct)
        tester(rec(e))

      case al @ ArraySelect(a, i) =>
        val tpe = normalizeType(a.getType)

        val sa = rec(a)
        val content = selectors.toB((tpe, 1))(sa)

        z3.mkSelect(content, rec(i))

      case al @ ArrayUpdated(a, i, e) =>
        val tpe = normalizeType(a.getType)

        val sa = rec(a)
        val ssize    = selectors.toB((tpe, 0))(sa)
        val scontent = selectors.toB((tpe, 1))(sa)

        val newcontent = z3.mkStore(scontent, rec(i), rec(e))

        val constructor = constructors.toB(tpe)

        constructor(ssize, newcontent)

      case al @ ArrayLength(a) =>
        val tpe = normalizeType(a.getType)
        val sa = rec(a)
        selectors.toB((tpe, 0))(sa)

      case arr @ FiniteArray(elems, oDefault, length) =>
        val at @ ArrayType(base) = normalizeType(arr.getType)
        typeToSort(at)

        val default = oDefault.getOrElse(simplestValue(base))

        val ar = rec(RawArrayValue(Int32Type, elems.map {
          case (i, e) => IntLiteral(i) -> e
        }, default))

        constructors.toB(at)(rec(length), ar)

      case f @ FunctionInvocation(tfd, args) =>
        z3.mkApp(functionDefToDecl(tfd), args.map(rec): _*)

      case fa @ Application(caller, args) =>
        val ft @ FunctionType(froms, to) = normalizeType(caller.getType)
        val funDecl = lambdas.cachedB(ft) {
          val sortSeq    = (ft +: froms).map(tpe => typeToSort(tpe))
          val returnSort = typeToSort(to)

          val name = FreshIdentifier("dynLambda").uniqueName
          z3.mkFreshFuncDecl(name, sortSeq, returnSort)
        }
        z3.mkApp(funDecl, (caller +: args).map(rec): _*)

      case ElementOfSet(e, s) => z3.mkSetMember(rec(e), rec(s))
      case SubsetOf(s1, s2) => z3.mkSetSubset(rec(s1), rec(s2))
      case SetIntersection(s1, s2) => z3.mkSetIntersect(rec(s1), rec(s2))
      case SetUnion(s1, s2) => z3.mkSetUnion(rec(s1), rec(s2))
      case SetDifference(s1, s2) => z3.mkSetDifference(rec(s1), rec(s2))
      case f @ FiniteSet(elems, base) => elems.foldLeft(z3.mkEmptySet(typeToSort(base)))((ast, el) => z3.mkSetAdd(ast, rec(el)))

      case RawArrayValue(keyTpe, elems, default) =>
        val ar = z3.mkConstArray(typeToSort(keyTpe), rec(default))

        elems.foldLeft(ar) {
          case (array, (k, v)) => z3.mkStore(array, rec(k), rec(v))
        }

      /**
       * ===== Map operations =====
       */
      case m @ FiniteMap(elems, from, to) =>
        val MapType(_, t) = normalizeType(m.getType)

        rec(RawArrayValue(from, elems.map{
          case (k, v) => (k, CaseClass(library.someType(t), Seq(v)))
        }.toMap, CaseClass(library.noneType(t), Seq())))

      case MapApply(m, k) =>
        val mt @ MapType(_, t) = normalizeType(m.getType)
        typeToSort(mt)

        val el = z3.mkSelect(rec(m), rec(k))

        // Really ?!? We don't check that it is actually != None?
        selectors.toB(library.someType(t), 0)(el)

      case MapIsDefinedAt(m, k) =>
        val mt @ MapType(_, t) = normalizeType(m.getType)
        typeToSort(mt)

        val el = z3.mkSelect(rec(m), rec(k))

        testers.toB(library.someType(t))(el)

      case MapUnion(m1, FiniteMap(elems, _, _)) =>
        val mt @ MapType(_, t) = normalizeType(m1.getType)
        typeToSort(mt)

        elems.foldLeft(rec(m1)) { case (m, (k,v)) =>
          z3.mkStore(m, rec(k), rec(CaseClass(library.someType(t), Seq(v))))
        }


      case gv @ GenericValue(tp, id) =>
        z3.mkApp(genericValueToDecl(gv))

      case other =>
        unsupported(other)
    }

    rec(expr)
  }

  protected[leon] def fromZ3Formula(model: Z3Model, tree: Z3AST, tpe: TypeTree): Expr = {

    def rec(t: Z3AST, tpe: TypeTree): Expr = {
      val kind = z3.getASTKind(t)
      kind match {
        case Z3NumeralIntAST(Some(v)) =>
          val leading = t.toString.substring(0, 2 min t.toString.length)
          if(leading == "#x") {
            _root_.smtlib.common.Hexadecimal.fromString(t.toString.substring(2)) match {
              case Some(hexa) =>
                tpe match {
                  case Int32Type => IntLiteral(hexa.toInt)
                  case CharType  => CharLiteral(hexa.toInt.toChar)
                  case IntegerType => InfiniteIntegerLiteral(BigInt(hexa.toInt))
                  case other =>
                    unsupported(other, "Unexpected target type for BV value")
                }
              case None => unsound(t, "could not translate hexadecimal Z3 numeral")
              }
          } else {
            tpe match {
              case Int32Type => IntLiteral(v)
              case CharType  => CharLiteral(v.toChar)
              case IntegerType => InfiniteIntegerLiteral(BigInt(v))
              case other =>
                unsupported(other, "Unexpected type for BV value: " + other)
            } 
          }

        case Z3NumeralIntAST(None) =>
          val ts = t.toString
          reporter.ifDebug(printer => printer(ts))(DebugSectionSynthesis)
          if(ts.length > 4 && ts.substring(0, 2) == "bv" && ts.substring(ts.length - 4) == "[32]") {
            val integer = ts.substring(2, ts.length - 4)
            tpe match {
              case Int32Type => 
                IntLiteral(integer.toLong.toInt)
              case CharType  => CharLiteral(integer.toInt.toChar)
              case IntegerType => 
                InfiniteIntegerLiteral(BigInt(integer))
              case _ =>
                reporter.fatalError("Unexpected target type for BV value: " + tpe.asString)
            }
          } else {  
            _root_.smtlib.common.Hexadecimal.fromString(t.toString.substring(2)) match {
              case Some(hexa) =>
                tpe match {
                  case Int32Type => IntLiteral(hexa.toInt)
                  case CharType  => CharLiteral(hexa.toInt.toChar)
                  case _ => unsound(t, "unexpected target type for BV value: " + tpe.asString)
                }
              case None => unsound(t, "could not translate Z3NumeralIntAST numeral")
            }
          }

        case Z3NumeralRealAST(n: BigInt, d: BigInt) => FractionalLiteral(n, d)

        case Z3AppAST(decl, args) =>
          val argsSize = args.size
          if(argsSize == 0 && (variables containsB t)) {
            variables.toA(t)
          } else if(functions containsB decl) {
            val tfd = functions.toA(decl)
            assert(tfd.params.size == argsSize)
            FunctionInvocation(tfd, args.zip(tfd.params).map{ case (a, p) => rec(a, p.getType) })
          } else if (generics containsB decl)  {
            generics.toA(decl)
          } else if (constructors containsB decl) {
            constructors.toA(decl) match {
              case cct: CaseClassType =>
                CaseClass(cct, args.zip(cct.fieldsTypes).map { case (a, t) => rec(a, t) })

              case UnitType =>
                UnitLiteral()

              case TupleType(ts) =>
                tupleWrap(args.zip(ts).map { case (a, t) => rec(a, t) })

              case ArrayType(to) =>
                val size = rec(args(0), Int32Type)
                val map  = rec(args(1), RawArrayType(Int32Type, to))

                (size, map) match {

                  case (s : IntLiteral, RawArrayValue(_, elems, default)) =>

                    if (s.value < 0)
                      unsupported(s, s"Z3 returned array of negative size")

                    val entries = elems.map {
                      case (IntLiteral(i), v) => i -> v
                      case (e,_) => unsupported(e, s"Z3 returned unexpected array index ${e.asString}")
                    }

                    finiteArray(entries, Some(default, s), to)
                  case (s : IntLiteral, arr) => unsound(args(1), "invalid array type")
                  case (size, _) => unsound(args(0), "invalid array size")
                }
            }
          } else {
            tpe match {
              case RawArrayType(from, to) =>
                model.getArrayValue(t) match {
                  case Some((z3map, z3default)) =>
                    val default = rec(z3default, to)
                    val entries = z3map.map {
                      case (k,v) => (rec(k, from), rec(v, to))
                    }

                    RawArrayValue(from, entries, default)
                  case None => unsound(t, "invalid array AST")
                }

              case ft @ FunctionType(fts, tt) => lambdas.getB(ft) match {
                case None => simplestValue(ft)
                case Some(decl) => model.getModelFuncInterpretations.find(_._1 == decl) match {
                  case None => simplestValue(ft)
                  case Some((_, mapping, elseValue)) =>
                    val leonElseValue = rec(elseValue, tt)
                    PartialLambda(mapping.flatMap { case (z3Args, z3Result) =>
                      if (t == z3Args.head) {
                        List((z3Args.tail zip fts).map(p => rec(p._1, p._2)) -> rec(z3Result, tt))
                      } else {
                        Nil
                      }
                    }, Some(leonElseValue), ft)
                }
              }

              case tp: TypeParameter =>
                val id = t.toString.split("!").last.toInt
                GenericValue(tp, id)

              case MapType(from, to) =>
                rec(t, RawArrayType(from, library.optionType(to))) match {
                  case r: RawArrayValue =>
                    // We expect a RawArrayValue with keys in from and values in Option[to],
                    // with default value == None
                    if (r.default.getType != library.noneType(to)) {
                      unsupported(r, "Solver returned a co-finite set which is not supported.")
                    }
                    require(r.keyTpe == from, s"Type error in solver model, expected ${from.asString}, found ${r.keyTpe.asString}")

                    val elems = r.elems.flatMap {
                      case (k, CaseClass(leonSome, Seq(x))) => Some(k -> x)
                      case (k, _) => None
                    }.toMap

                    FiniteMap(elems, from, to)
                }

              case tpe @ SetType(dt) =>
                model.getSetValue(t) match {
                  case None => unsound(t, "invalid set AST")
                  case Some(set) =>
                    val elems = set.map(e => rec(e, dt))
                    FiniteSet(elems, dt)
                }

              case _ =>
                import Z3DeclKind._
                z3.getDeclKind(decl) match {
                  case OpTrue =>    BooleanLiteral(true)
                  case OpFalse =>   BooleanLiteral(false)
            //      case OpEq =>      Equals(rargs(0), rargs(1))
            //      case OpITE =>     IfExpr(rargs(0), rargs(1), rargs(2))
            //      case OpAnd =>     andJoin(rargs)
            //      case OpOr =>      orJoin(rargs)
            //      case OpIff =>     Equals(rargs(0), rargs(1))
            //      case OpXor =>     not(Equals(rargs(0), rargs(1)))
            //      case OpNot =>     not(rargs(0))
            //      case OpImplies => implies(rargs(0), rargs(1))
            //      case OpLE =>      LessEquals(rargs(0), rargs(1))
            //      case OpGE =>      GreaterEquals(rargs(0), rargs(1))
            //      case OpLT =>      LessThan(rargs(0), rargs(1))
            //      case OpGT =>      GreaterThan(rargs(0), rargs(1))
            //      case OpAdd =>     Plus(rargs(0), rargs(1))
            //      case OpSub =>     Minus(rargs(0), rargs(1))
                  case OpUMinus =>  UMinus(rec(args(0), tpe))
            //      case OpMul =>     Times(rargs(0), rargs(1))
            //      case OpDiv =>     Division(rargs(0), rargs(1))
            //      case OpIDiv =>    Division(rargs(0), rargs(1))
            //      case OpMod =>     Modulo(rargs(0), rargs(1))
                  case other => unsound(t, 
                      s"""|Don't know what to do with this declKind: $other
                          |Expected type: ${Option(tpe).map{_.asString}.getOrElse("")}
                          |Tree: $t
                          |The arguments are: $args""".stripMargin
                    )
                }
            }
          }
        case _ => unsound(t, "unexpected AST")
      }
    }

    rec(tree, normalizeType(tpe))
  }

  protected[leon] def softFromZ3Formula(model: Z3Model, tree: Z3AST, tpe: TypeTree) : Option[Expr] = {
    try {
      Some(fromZ3Formula(model, tree, tpe))
    } catch {
      case e: Unsupported => None
      case e: UnsoundExtractionException => None
      case n: java.lang.NumberFormatException => None
    }
  }

  def idToFreshZ3Id(id: Identifier): Z3AST = {
    z3.mkFreshConst(id.uniqueName, typeToSort(id.getType))
  }

  def reset() = {
    throw new CantResetException(this)
  }

}