
abstract class Term
case class Var(s: String) extends Term
case class Fun(s: String, ts: List[Term]) extends Term


abstract class Form
object True extends Form
object False extends Form
case class Pred(s: String, ts: List[Term]) extends Form
case class Imp(f1: Form, f2: Form) extends Form
case class Says(p: String, f: Form) extends Form
case class And(f1: Form, f2: Form) extends Form
case class Or(f1: Form, f2: Form) extends Form
case class Controls(p: String, f: Form) extends Form

case class Judgement(Gamma: List[Form], F: Form) {
  def lhs = Gamma
  def rhs = F
}

val Alice = "Alice"
val Bob = "Bob"
val Send = Pred("Send", Nil)

val Gamma = 
  List( Imp(Says(Bob, Send), Send),
        Says(Bob, Imp(Says(Alice, Send), Send)),
        Says(Alice, Send) )

val goal = Judgement(Gamma, Send)

def sc () = { println ("Yes!") }

class Main {

def prove(j: Judgement, sc: () => Unit) : Unit = j match {
  case Judgement(lhs, rhs) => 
    { if (lhs.exists(f => f == rhs))  sc () 
      else prove1(lhs, rhs, sc) 
    }
}

def partitions [A] (l: List[A]): List[(A, List[A])]  = 
  l.map (s => (s, l - s))

def prove1(lhs: List[Form], rhs: Form, sc: () => Unit) : Unit = 
  rhs match {
    case Imp(f1, f2) => prove(Judgement(f1::lhs, f2), sc)
    /*case Says(p, f) => prove(Judgement(lhs, f), sc)*/
    /*case Controls(p, f) => prove(Judgement(lhs, f), sc)*/
    case Or(f1, f2) => { prove(Judgement(lhs, f1), sc);
                         prove(Judgement(lhs, f2), sc) }
    case And(f1, f2) => prove(Judgement(lhs, f1), 
                            () => prove(Judgement(lhs, f2), sc))
    case _ => { for ((f, lhs_rest) <- partitions(lhs))
                  prove2(f, lhs_rest, rhs, sc) }
  }

def prove2(f: Form, lhs_rest: List[Form], rhs: Form, sc: () => Unit) : Unit = 
  f match {
    case Imp(f1, f2) => 
      prove(Judgement(lhs_rest, f1), 
            () => prove(Judgement(f2::lhs_rest, rhs), sc))
    case Says(p, Imp(f1, f2)) => 
      prove(Judgement(lhs_rest, Says(p, f1)), 
            () => prove(Judgement(Says(p, f2)::lhs_rest, rhs), sc)) 
    case Controls(p, f) =>
      prove(Judgement(lhs_rest, Says(p, f)),
            () => prove(Judgement(f::lhs_rest, rhs), sc))
    case _ => ()
  }

}
  
val main = new Main
val Foo = Pred("Foo", Nil)

main.prove (Judgement (Gamma, And(Foo, Send)), sc)
main.prove (Judgement (Nil, Foo), sc)
main.prove (Judgement (Nil, Imp(Send, Send)), sc)
main.prove (Judgement (Gamma, Send), sc)
main.prove (Judgement (Gamma, Foo), sc)

val F1 = Imp(Says(Bob, Send), Send)
val F2 = Says(Bob, Imp(Says(Alice, Send), Send))
val F3 = Says(Alice, Send)

main.prove (Judgement (Nil, Imp(F1, Imp(F2, Imp(F3, Send)))), sc)

val Server = "Server"

def Sends(p: String, q: String, f: Form) : Form =
  Imp(Says(p, f), Says(q, f))

def Enc(f: Form, k: Form) : Form = Imp(k, f)

def Connect(p: String, q: String) : Form =
  Pred("Connect", List(Var(p), Var(q)))

val Msg = Pred("Msg", Nil)
val Kas = Pred("Kas", Nil)
val Kbs = Pred("Kbs", Nil)
val Kab = Pred("Kab", Nil)

val Gamma_big = 
  List( Says(Alice, Kas),
        Says(Bob, Kbs),
        Says(Alice, Msg),
        Says(Alice, Connect(Alice, Bob)),
        Sends(Alice, Server, Connect(Alice, Bob)),
        Says(Server, Imp(Connect(Alice, Bob), Enc(Kab, Kas))),
        Says(Server, Imp(Connect(Alice, Bob), Enc(Enc(Kab, Kbs), Kas))),
        Sends(Server, Alice, Enc(Kab, Kas)),
        Sends(Server, Alice, Enc(Enc(Kab, Kbs), Kas)),
        Sends(Alice, Bob, Enc(Kab, Kbs)),
        Sends(Alice, Bob, Enc(Msg, Kab))
      )


main.prove (Judgement(Gamma_big, Says(Bob, Msg)), sc)
