= Preface

== Welcome

This electronic book is a course on _Software Foundations_, the mathematical
underpinnings of reliable software. Topics include basic concepts of logic,
computer-assisted theorem proving, the Idris programming language, functional
programming, operational semantics, Hoare logic, and static type systems. The
exposition is intended for a broad range of readers, from advanced
undergraduates to PhD students and researchers. No specific background in logic
or programming languages is assumed, though a degree of mathematical maturity
will be helpful.

The principal novelty of the course is that it is one hundred percent formalized
and machine-checked: the entire text is Literate Idris. It is intended to be
read alongside an interactive session with Idris. All the details in the text
are fully formalized in Idris, and the exercises are designed to be worked using
Idris.

The files are organized into a sequence of core chapters, covering about one
semester's worth of material and organized into a coherent linear narrative,
plus a number of "appendices" covering additional topics. All the core chapters
are suitable for both upper-level undergraduate and graduate students.

== Overview

Building reliable software is hard. The scale and complexity of modern systems,
the number of people involved in building them, and the range of demands placed
on them render it extremely difficult to build software that is even
more-or-less correct, much less 100% correct. At the same time, the increasing
degree to which information processing is woven into every aspect of society
continually amplifies the cost of bugs and insecurities.

Computer scientists and software engineers have responded to these challenges by
developing a whole host of techniques for improving software reliability,
ranging from recommendations about managing software projects and organizing
programming teams (e.g., extreme programming) to design philosophies for
libraries (e.g., model-view-controller, publish-subscribe, etc.) and programming
languages (e.g., object-oriented programming, aspect-oriented programming,
functional programming, ...) to mathematical techniques for specifying and
reasoning about properties of software and tools for helping validate these
properties.

The present course is focused on this last set of techniques. The text weaves
together five conceptual threads:

1. basic tools from _logic_ for making and justifying precise claims about
   programs;
2. the use of _proof assistants_ to construct rigorous logical arguments;

3. the idea of _functional programming_, both as a method of programming that
   simplifies reasoning about programs and as a bridge between programming and
   logic;

4. formal techniques for _reasoning about the properties of specific programs_
   (e.g., the fact that a sorting function or a compiler obeys some formal
   specification); and

5. the use of _type systems_ for establishing well-behavedness guarantees for
   _all_ programs in a given programming language (e.g., the fact that
   well-typed Java programs cannot be subverted at runtime).

Each of these topics is easily rich enough to fill a whole course in its own
right, so tackling all of them together naturally means that much will be left
unsaid. Nevertheless, we hope readers will find that the themes illuminate and
amplify each other and that bringing them together creates a foundation from
which it will be easy to dig into any of them more deeply. Some suggestions for
further reading can be found in the [Postscript] chapter. Bibliographic
information for all cited works can be found in the [Bib] chapter.

=== Logic

Logic is the field of study whose subject matter is _proofs_ -- unassailable
arguments for the truth of particular propositions. Volumes have been written
about the central role of logic in computer science. Manna and Waldinger called
it "the calculus of computer science," while Halpern et al.'s paper _On the
Unusual Effectiveness of Logic in Computer Science_ catalogs scores of ways in
which logic offers critical tools and insights. Indeed, they observe that "As a
matter of fact, logic has turned out to be significantly more effective in
computer science than it has been in mathematics. This is quite remarkable,
especially since much of the impetus for the development of logic during the
past one hundred years came from mathematics."

In particular, the fundamental notion of inductive proofs is ubiquitous in all
of computer science. You have surely seen them before, in contexts from discrete
math to analysis of algorithms, but in this course we will examine them much
more deeply than you have probably done so far.

=== Proof Assistants

The flow of ideas between logic and computer science has not been in just one
direction: CS has also made important contributions to logic. One of these has
been the development of software tools for helping construct proofs of logical
propositions. These tools fall into two broad categories:

  - _Automated theorem provers_ provide "push-button" operation: you give them a
    proposition and they return either _true_, _false_, or _ran out of time_.
    Although their capabilities are limited to fairly specific sorts of
    reasoning, they have matured tremendously in recent years and are used now
    in a huge variety of settings. Examples of such tools include SAT solvers,
    SMT solvers, and model checkers.

  - _Proof assistants_ are hybrid tools that automate the more routine aspects
    of building proofs while depending on human guidance for more difficult
    aspects. Widely used proof assistants include Isabelle, Agda, Twelf, ACL2,
    PVS, Coq, and Idris among many others.

This course is based around Coq, a proof assistant that has been under
development, mostly in France, since 1983 and that in recent years has attracted
a large community of users in both research and industry. Coq provides a rich
environment for interactive development of machine-checked formal reasoning. The
kernel of the Coq system is a simple proof-checker, which guarantees that only
correct deduction steps are performed. On top of this kernel, the Coq
environment provides high-level facilities for proof development, including
powerful tactics for constructing complex proofs semi-automatically, and a large
library of common definitions and lemmas.

Coq has been a critical enabler for a huge variety of work across computer
science and mathematics:

  - As a _platform for modeling programming languages_, it has become a standard
    tool for researchers who need to describe and reason about complex language
    definitions. It has been used, for example, to check the security of the
    JavaCard platform, obtaining the highest level of common criteria
    certification, and for formal specifications of the x86 and LLVM instruction
    sets and programming languages such as C.

  - As an _environment for developing formally certified software_, Coq has been
    used, for example, to build CompCert, a fully-verified optimizing compiler
    for C, for proving the correctness of subtle algorithms involving floating
    point numbers, and as the basis for CertiCrypt, an environment for reasoning
    about the security of cryptographic algorithms.

  - As a _realistic environment for functional programming with dependent
    types_, it has inspired numerous innovations. For example, the Ynot project
    at Harvard embedded "relational Hoare reasoning" (an extension of the _Hoare
    Logic_ we will see later in this course) in Coq.

  - As a _proof assistant for higher-order logic_, it has been used to validate
    a number of important results in mathematics. For example, its ability to
    include complex computations inside proofs made it possible to develop the
    first formally verified proof of the 4-color theorem. This proof had
    previously been controversial among mathematicians because part of it
    included checking a large number of configurations using a program. In the
    Coq formalization, everything is checked, including the correctness of the
    computational part. More recently, an even more massive effort led to a Coq
    formalization of the Feit-Thompson Theorem -- the first major step in the
    classification of finite simple groups.

By the way, in case you're wondering about the name, here's what the official
Coq web site says: "Some French computer scientists have a tradition of naming
their software as animal species: Caml, Elan, Foc or Phox are examples of this
tacit convention. In French, 'coq' means rooster, and it sounds like the
initials of the Calculus of Constructions (CoC) on which it is based." The
rooster is also the national symbol of France, and C-o-q are the first three
letters of the name of Thierry Coquand, one of Coq's early developers.

=== Functional Programming

The term _functional programming_ refers both to a collection of programming
idioms that can be used in almost any programming language and to a family of
programming languages designed to emphasize these idioms, including Haskell,
OCaml, Standard ML, F#, Scala, Scheme, Racket, Common Lisp, Clojure, Erlang,
and Coq.

Functional programming has been developed over many decades -- indeed, its roots
go back to Church's lambda-calculus, which was invented in the 1930s, before
there were even any computers! But since the early '90s it has enjoyed a surge
of interest among industrial engineers and language designers, playing a key
role in high-value systems at companies like Jane St. Capital, Microsoft,
Facebook, and Ericsson.

The most basic tenet of functional programming is that, as much as possible,
computation should be _pure_, in the sense that the only effect of execution
should be to produce a result: the computation should be free from _side
effects_ such as I/O, assignments to mutable variables, redirecting pointers,
etc. For example, whereas an _imperative_ sorting function might take a list of
numbers and rearrange its pointers to put the list in order, a pure sorting
function would take the original list and return a _new_ list containing the
same numbers in sorted order.

One significant benefit of this style of programming is that it makes programs
easier to understand and reason about. If every operation on a data structure
yields a new data structure, leaving the old one intact, then there is no need
to worry about how that structure is being shared and whether a change by one
part of the program might break an invariant that another part of the program
relies on. These considerations are particularly critical in concurrent
programs, where every piece of mutable state that is shared between threads is a
potential source of pernicious bugs. Indeed, a large part of the recent interest
in functional programming in industry is due to its simpler behavior in the
presence of concurrency.

Another reason for the current excitement about functional programming is
related to the first: functional programs are often much easier to parallelize
than their imperative counterparts. If running a computation has no effect other
than producing a result, then it does not matter _where_ it is run. Similarly,
if a data structure is never modified destructively, then it can be copied
freely, across cores or across the network. Indeed, the "Map-Reduce" idiom,
which lies at the heart of massively distributed query processors like Hadoop
and is used by Google to index the entire web is a classic example of functional
programming.

For this course, functional programming has yet another significant attraction:
it serves as a bridge between logic and computer science. Indeed, Coq itself can
be viewed as a combination of a small but extremely expressive functional
programming language plus with a set of tools for stating and proving logical
assertions. Moreover, when we come to look more closely, we find that these two
sides of Coq are actually aspects of the very same underlying machinery -- i.e.,
_proofs are programs_.

=== Program Verification

Approximately the first third of the book is devoted to developing the
conceptual framework of logic and functional programming and gaining enough
fluency with Coq to use it for modeling and reasoning about nontrivial
artifacts. From this point on, we increasingly turn our attention to two broad
topics of critical importance to the enterprise of building reliable software
(and hardware): techniques for proving specific properties of particular
_programs_ and for proving general properties of whole programming _languages_.

For both of these, the first thing we need is a way of representing programs as
mathematical objects, so we can talk about them precisely, together with ways of
describing their behavior in terms of mathematical functions or relations. Our
tools for these tasks are _abstract syntax_ and _operational semantics_, a
method of specifying programming languages by writing abstract interpreters. At
the beginning, we work with operational semantics in the so-called "big-step"
style, which leads to somewhat simpler and more readable definitions when it is
applicable. Later on, we switch to a more detailed "small-step" style, which
helps make some useful distinctions between different sorts of "nonterminating"
program behaviors and is applicable to a broader range of language features,
including concurrency.

The first programming language we consider in detail is _Imp_, a tiny toy
language capturing the core features of conventional imperative programming:
variables, assignment, conditionals, and loops. We study two different ways of
reasoning about the properties of Imp programs.

First, we consider what it means to say that two Imp programs are _equivalent_
in the intuitive sense that they yield the same behavior when started in any
initial memory state. This notion of equivalence then becomes a criterion for
judging the correctness of _metaprograms_ -- programs that manipulate other
programs, such as compilers and optimizers. We build a simple optimizer for Imp
and prove that it is correct.

Second, we develop a methodology for proving that particular Imp programs
satisfy formal specifications of their behavior. We introduce the notion of
_Hoare triples_ -- Imp programs annotated with pre- and post-conditions
describing what should be true about the memory in which they are started and
what they promise to make true about the memory in which they terminate -- and
the reasoning principles of _Hoare Logic_, a "domain-specific logic" specialized
for convenient compositional reasoning about imperative programs, with concepts
like "loop invariant" built in.

This part of the course is intended to give readers a taste of the key ideas and
mathematical tools used in a wide variety of real-world software and hardware
verification tasks.

=== Type Systems

Our final major topic, covering approximately the last third of the course, is
_type systems_, a powerful set of tools for establishing properties of _all_
programs in a given language.

Type systems are the best established and most popular example of a highly
successful class of formal verification techniques known as _lightweight formal
methods_. These are reasoning techniques of modest power -- modest enough that
automatic checkers can be built into compilers, linkers, or program analyzers
and thus be applied even by programmers unfamiliar with the underlying theories.
Other examples of lightweight formal methods include hardware and software model
checkers, contract checkers, and run-time property monitoring techniques for
detecting when some component of a system is not behaving according to
specification.

This topic brings us full circle: the language whose properties we study in this
part, the _simply typed lambda-calculus_, is essentially a simplified model of
the core of Coq itself!

=== Further Reading

This text is intended to be self contained, but readers looking for a deeper
treatment of a particular topic will find suggestions for further reading in the
[Postscript] chapter.

== Practicalities

=== Chapter Dependencies

A diagram of the dependencies between chapters and some suggested
paths through the material can be found in the file [deps.html].

=== System Requirements

Coq runs on Windows, Linux, and OS X.  You will need:

  - A current installation of Coq, available from the Coq home page. Everything
    should work with version 8.4. (Version 8.5 will _not_ work, due to a few
    incompatible changes in Coq between 8.4 and 8.5.)

  - An IDE for interacting with Coq. Currently, there are two choices:

    - Proof General is an Emacs-based IDE. It tends to be preferred by users who
      are already comfortable with Emacs. It requires a separate installation
      (google "Proof General").

    - CoqIDE is a simpler stand-alone IDE. It is distributed with Coq, so it
      should "just work" once you have Coq installed. It can also be compiled
      from scratch, but on some platforms this may involve installing additional
      packages for GUI libraries and such.

=== Exercises

Each chapter includes numerous exercises. Each is marked with a "star rating,"
which can be interpreted as follows:

  - One star: easy exercises that underscore points in the text and that, for
    most readers, should take only a minute or two. Get in the habit of working
    these as you reach them.

  - Two stars: straightforward exercises (five or ten minutes).

  - Three stars: exercises requiring a bit of thought (ten minutes to half an
    hour).

  - Four and five stars: more difficult exercises (half an hour and up).

Also, some exercises are marked "advanced", and some are marked "optional."
Doing just the non-optional, non-advanced exercises should provide good coverage
of the core material. Optional exercises provide a bit of extra practice with
key concepts and introduce secondary themes that may be of interest to some
readers. Advanced exercises are for readers who want an extra challenge (and, in
return, a deeper contact with the material).

_Please do not post solutions to the exercises in any public place_: Software
Foundations is widely used both for self-study and for university courses.
Having solutions easily available makes it much less useful for courses, which
typically have graded homework assignments. The authors especially request that
readers not post solutions to the exercises anyplace where they can be found by
search engines.

=== Downloading the Coq Files

A tar file containing the full sources for the "release version" of these notes
(as a collection of Coq scripts and HTML files) is available here:

    http://www.cis.upenn.edu/~bcpierce/sf

If you are using the notes as part of a class, you may be given access to a
locally extended version of the files, which you should use instead of the
release version.

== Translations

Thanks to the efforts of a team of volunteer translators, _Software Foundations_
can now be enjoyed in Japanese at [http://proofcafe.org/sf]. A Chinese
translation is underway.


module Syntax

public export
data Expression
  = Numeral Integer
  | Plus Expression Expression
  | Minus Expression Expression
  | Times Expression Expression
  | Divide Expression Expression
  | Negate Expression


module Main

import Control.Monad.State
import Data.Fin
import Data.List
import Data.Nat
import Data.SortedMap
import Data.Vect

data Wire : Type where
  MkWire : Int -> Wire

Eq Wire where
  (MkWire a) == (MkWire b) = a == b

Ord Wire where
  compare (MkWire a) (MkWire b) = compare a b

Show Wire where
  show (MkWire a) = "wire(" ++ show a ++ ")"

data Component : Type where
  Nand : (a : Wire) -> (b : Wire) -> (aNandB : Wire) -> Component
  One : (alwaysOne : Wire) -> Component
  Zero : (alwaysZero : Wire) -> Component
  Observer : (tag : String) -> Wire -> Component
  Relay : (in' : Wire) -> (out : Wire) -> Component
  -- Boot : Wire -> Component

Show Component where
  show (Nand a b o) = show o ++ "=" ++ "~(" ++ show a ++ "&" ++ show b ++ ")"
  show (One w) = show w ++ "=1"
  show (Zero w) = show w ++ "=0"
  show (Observer tag w) = tag ++ "=" ++ show w
  show (Relay i o) = show o ++ "=" ++ show i
  -- show (Boot w) = show w ++ "=<boot>"

Circuit : Type
Circuit = List Component

record BuilderState where
  constructor MkBuilderState
  wireCounter : Int
  circuit : List Component

CircuitBuilder : Type -> Type
CircuitBuilder = State BuilderState

runCircuitBuilder : CircuitBuilder a -> Circuit
runCircuitBuilder = circuit . execState (MkBuilderState 0 [])

newWire : CircuitBuilder Wire
newWire = do
  wire <- gets wireCounter
  modify record { wireCounter $= (+ 1) }
  pure (MkWire wire)

addComponents : List Component -> CircuitBuilder ()
addComponents ts = modify record { circuit $= (ts ++) }

observe : String -> Wire -> CircuitBuilder ()
observe tag w = addComponents [ Observer tag w ]

combinatorial : (Wire -> CircuitBuilder (List Component)) -> CircuitBuilder Wire
combinatorial f = do
  out <- newWire
  comps <- f out
  addComponents comps
  pure out

relay : Wire -> CircuitBuilder Wire
relay w = combinatorial \out =>
  pure [ Relay w out ]

relay_ : Wire -> Wire -> CircuitBuilder ()
relay_ i o = addComponents [ Relay i o ]

delay : Nat -> Wire -> CircuitBuilder Wire
delay 0 w = pure w
delay (S k) w = relay w >>= delay k

-- boot : Wire -> CircuitBuilder ()
-- boot w = addComponents [ Boot w ]

nand : Wire -> Wire -> CircuitBuilder Wire
nand a b = combinatorial \out =>
  pure [ Nand a b out ]

nand_ : Wire -> Wire -> Wire -> CircuitBuilder ()
nand_ a b o = addComponents [ Nand a b o ]

one : CircuitBuilder Wire
one = combinatorial \out =>
  pure [ One out ]

one_ : Wire -> CircuitBuilder ()
one_ w = addComponents [ One w ]

zero : CircuitBuilder Wire
zero = combinatorial \out =>
  pure [ Zero out ]

zero_ : Wire -> CircuitBuilder ()
zero_ w = addComponents [ Zero w ]

not : Wire -> CircuitBuilder Wire
not a = nand a !one

not_ : Wire -> Wire -> CircuitBuilder ()
not_ a na = addComponents [ Nand a !one na ]

and : Wire -> Wire -> CircuitBuilder Wire
and a b = not !(nand a b)

or : Wire -> Wire -> CircuitBuilder Wire
or a b = nand !(not a) !(not b)

xor : Wire -> Wire -> CircuitBuilder Wire
xor a b = and !(nand a b) !(or a b)

nor : Wire -> Wire -> CircuitBuilder Wire
nor a b = not !(or a b)

nor_ : Wire -> Wire -> Wire -> CircuitBuilder ()
nor_ a b o = not_ !(or a b) o

halfAdder : Wire -> Wire -> CircuitBuilder (Wire, Wire)
halfAdder a b = [| (xor a b, and a b) |]

fullAdder : Wire -> Wire -> Wire -> CircuitBuilder (Wire, Wire)
fullAdder a b carryIn = do
  (sum1, carry1) <- halfAdder a b
  (sum2, carry2) <- halfAdder sum1 carryIn
  pure (sum2, !(or carry1 carry2))

Byte : Type
Byte = Vect 8 Wire

dFoldL : {n : Nat}
  -> (0 motive : Nat -> Type)
  -> (forall p. motive p -> a -> motive (S p))
  -> motive 0
  -> Vect n a
  -> motive n
dFoldL {n} motive f acc v = rewrite sym $ plusZeroRightNeutral n in helper acc v
  where
    helper : {q, m : Nat} -> motive m -> Vect q a -> motive (q + m)
    helper acc Nil = acc
    helper {q = S q, m} acc (x :: xs) = rewrite plusSuccRightSucc q m in helper (f acc x) xs

byteAdder : Byte -> Byte -> Wire -> CircuitBuilder (Byte, Wire)
byteAdder as bs carryIn = dFoldL (\n => CircuitBuilder (Vect n Wire, Wire)) (\ms, ab => f !ms ab) (pure (Nil, carryIn)) (zipWith (,) as bs)
  where
    f : (Vect n Wire, Wire) -> (Wire, Wire) -> CircuitBuilder (Vect (S n) Wire, Wire)
    f (os, c) (a, b) = do
      (o, c') <- fullAdder a b c
      pure (snoc os o, c')

binsToConsts : Vect n (Fin 2) -> CircuitBuilder (Vect n Wire)
binsToConsts = traverse \b =>
  case b of
    1 => one
    0 => zero

-- binsToBootedWires : Vect n (Fin 2) -> CircuitBuilder (Vect n Wire)
-- binsToBootedWires = traverse \b => do
--   w <- newWire
--   if b == 1 then boot w else pure ()
--   pure w

clock : (halfWidthMinusOne : Nat) -> CircuitBuilder Wire
clock n = do
  w <- newWire
  i <- delay n w
  not_ i w
  pure i

gatedSRLatch_ : (set : Wire) -> (reset : Wire) -> (enable : Wire)
  -> (q : Wire) -> (nq : Wire) -> CircuitBuilder ()
gatedSRLatch_ s r e q nq= do
  s' <- and s e
  r' <- and r e
  nor_ r' nq q
  nor_ s' q nq
  pure ()

gatedSRLatch : (set : Wire) -> (reset : Wire) -> (enable : Wire) -> CircuitBuilder (Wire, Wire)
gatedSRLatch s r e = do
  q <- newWire
  nq <- newWire
  gatedSRLatch_ s r e q nq
  pure (q, nq)

gatedDLatch : (data' : Wire) -> (enable : Wire) -> CircuitBuilder (Wire, Wire)
gatedDLatch d e = gatedSRLatch d !(not d) e

dFlipFlop_ : (data' : Wire) -> (clock : Wire) -> (dataNext : Wire) -> CircuitBuilder ()
dFlipFlop_ d clk dn = do
  (q, nq) <- gatedDLatch d !(not clk)
  dummy <- newWire
  gatedSRLatch_ q nq clk dn dummy

dFlipFlop : (data' : Wire) -> (clock : Wire) -> CircuitBuilder Wire
dFlipFlop d clk = do
  dn <- newWire
  dFlipFlop_ d clk dn
  pure dn

data Signal = High | Low

Show Signal where
  show High = "1"
  show Low = "0"

data CircuitState
  = Valid (SortedMap Wire Signal)
  | Invalid

Show CircuitState where
  show (Valid cstate) = show cstate
  show Invalid = "invalid circuit state"

Semigroup CircuitState where
  Invalid <+> _ = Invalid
  _ <+> Invalid = Invalid
  Valid a <+> Valid b = toCS . sequence $ mergeWith m (map Just a) (map Just b)
    where
      m : Maybe Signal -> Maybe Signal -> Maybe Signal
      m (Just High) (Just High) = Just High
      m (Just Low) (Just Low) = Just Low
      m _ _ = Nothing

      toCS : Maybe (SortedMap Wire Signal) -> CircuitState
      toCS (Just cs) = Valid cs
      toCS Nothing = Invalid

Monoid CircuitState where
  neutral = Valid empty

lookupSignal : Wire -> CircuitState -> Maybe Signal
lookupSignal w (Valid cs) = lookup w cs
lookupSignal w Invalid = Nothing

initialState : Circuit -> CircuitState
initialState = concat . map toMap
  where
    toMap : Component -> CircuitState
    toMap (Nand a b c) = Valid . fromList $ [ (a, Low), (b, Low), (c, Low) ]
    toMap (One w) = Valid . fromList $ [ (w, Low) ]
    toMap (Zero w) = Valid . fromList $ [ (w, Low) ]
    toMap (Observer _ w) = Valid . fromList $ [ (w, Low) ]
    toMap (Relay i o) = Valid . fromList $ [ (i, Low), (o, Low) ]
    -- toMap (Boot w) = Valid . fromList $ [ (w, High) ]

count : (n : Nat) -> Vect n Nat
count 0 = Nil
count (S n) = snoc (count n) n

runCircuit : Circuit -> CircuitState-> CircuitState
runCircuit comps cstate = concat . map drive $ comps
  where
    signalNand : Signal -> Signal -> Signal
    signalNand High High = Low
    signalNand High Low = High
    signalNand Low High = High
    signalNand Low Low = High

    drive : Component -> CircuitState
    drive (Nand a b o) =
      let as = lookupSignal a cstate
          bs = lookupSignal b cstate
      in case (as, bs) of
        (Just asig, Just bsig) => Valid . fromList $ [ (o, signalNand asig bsig) ]
        _ => Invalid
    drive (One w) = Valid . fromList $ [ (w, High) ]
    drive (Zero w) = Valid . fromList $ [ (w, Low) ]
    drive (Observer _ w) = Valid empty
    drive (Relay i o) =
      case lookupSignal i cstate of
        Just isig => Valid . fromList $ [ (o, isig) ]
        Nothing => Invalid
    -- drive (Boot w) = Valid empty

Observations : Type
Observations = List (String, Signal)

getObservations : Circuit -> CircuitState -> Observations
getObservations comps (Valid cstate) = concatMap ob $ comps
  where
    ob : Component -> List (String, Signal)
    ob (Observer tag w) =
      case lookup w cstate of
        Just sig => [ (tag, sig) ]
        Nothing => []
    ob _ = []
getObservations _ Invalid = []

putObservationsLn : Observations -> IO ()
putObservationsLn = (>> putChar '\n') . traverse f
  where
    f : (String, Signal) -> IO ()
    f (tag, sig) = putStr $ tag ++ "=" ++ show sig ++ ", "

exampleAdder : Circuit
exampleAdder = runCircuitBuilder $ do
  const2 <- binsToConsts [ 0, 1, 0, 0, 0, 0, 0, 0 ]
  const3 <- binsToConsts [ 1, 1, 0, 0, 0, 0, 0, 0 ]
  (shouldBe5, shouldBe0) <- byteAdder const2 const3 !zero
  let indexed5 = zipWith (,) shouldBe5 (count 8)
  traverse (\(w, n) => observe ("out[" ++ show n ++ "]") w) indexed5
  observe "out_carry" shouldBe0

exampleClock : Nat -> Circuit
exampleClock n = runCircuitBuilder $ do
  c <- clock n
  observe "clock" c

exampleLatch : Circuit
exampleLatch = runCircuitBuilder $ do
  (q, nq) <- gatedSRLatch !zero !one !one
  observe "q" q
  observe "~q" nq

exampleDff : Circuit
exampleDff = runCircuitBuilder $ do
  clk <- clock 40
  observe "clk" clk
  dFlipFlop !one clk >>= observe "dn"

main : IO ()
main =
  let exm = exampleDff
  in map (const ()) . traverse (putObservationsLn . getObservations exm) . iterateN 200 (runCircuit exm) . initialState $ exm


identity : (a : Type) -> a -> a
identity a = id

identityL :
  (a, b : Type )
  -> (f : a -> b)
  -> (identity b) . f = f
identityL a b f = Refl

identityR :
  (a, b : Type )
  -> (f : a -> b)
  -> f = (identity b) . f
identityR a b f = Refl


module Typing.Infer

import public Typing.Term
import Syntax.Term as Untyped

import Control.Monad.State.State
import Control.Monad.State.Interface
import Control.Monad.Identity
import Control.Monad.Error.Either
import Control.Monad.Error.Interface
import Control.Monad.Trans

import Data.SortedMap
import Data.List

-- Some helper functions

||| It's like A / B to multiset
complement : Eq a => List a -> List a -> List a
complement = foldl (flip Data.List.delete)

mapMap : Ord k => (v -> n) -> SortedMap k v -> SortedMap k n
mapMap f sMap =
    let vals = toList sMap in
    foldr (\c => insert (Builtin.fst c) (f $ Builtin.snd c)) empty vals

||| Substitutes freeVars for concrete types

Subst : Type
Subst = SortedMap String Ty

emptySubst : Subst
emptySubst = empty

||| Type enviroment that stores all mapping from strings to schemas
data Context = MkCtx (SortedMap (List String) Schema)

remove : Context -> List String -> Context
remove (MkCtx ctx) str = MkCtx (delete str ctx)

findVar : (List String) -> Context -> Maybe Schema
findVar key (MkCtx ctx) = lookup key ctx

insert : Context -> (List String) -> Schema -> Context
insert (MkCtx ctx) key val = MkCtx (insert key val ctx)

-- Apply and freevars implementation
interface Application a where
  freeVars : a -> (List String)
  apply : Subst -> a -> a

Application Ty where
  freeVars (TyVar r n) = [n]
  freeVars (TyPoly r path ty) = freeVars ty
  freeVars (TySimple r s) = []
  freeVars (TyArrow r a b) = (freeVars a) `union` (freeVars b)

  apply subs t@(TyVar r n) =
    case lookup n subs of
      Just val => val
      Nothing  => t

  apply subs (TyArrow r arg ret) = TyArrow r (apply subs arg) (apply subs ret)
  apply subs (TyPoly r name type) = TyPoly r name (apply subs type)
  apply subs (TySimple r t) = TySimple r t

Application Schema where
  freeVars (MkSchema binded ty)   = freeVars ty `complement` binded
  apply subs (MkSchema binded ty) =
    let bindFreeSubs = foldr Data.SortedMap.delete subs binded in
    MkSchema binded (apply bindFreeSubs ty)

Application a => Application (List a) where
  apply      = map . apply
  freeVars   = foldr union [] . map freeVars

Application Context where
  freeVars (MkCtx env) = freeVars (values env)
  apply subs (MkCtx env) = MkCtx (mapMap (apply subs) env)

composeSubst : Subst -> Subst -> Subst
composeSubst s1 s2 = (mapMap (apply s1) s2) `mergeLeft` (s1)

-- Some functions of Hindley milner

TI : Type -> Type
TI = StateT Int (EitherT String Identity)

newTyVar : TI Ty
newTyVar = do
  res <- get
  put (res + 1)
  pure (TyVar emptyRange (cast $ res + 1))

generalize : Context -> Ty -> Schema
generalize ctx ty =
  let vars = freeVars ty `complement` freeVars ctx in
  MkSchema vars ty

instantiate : Schema -> TI Ty
instantiate (MkSchema vars ty) = do
  nvars <- traverse (const newTyVar) vars
  let subst = fromList $ zip vars nvars
  pure (apply subst ty)

-- Unification

isJust : Maybe a -> Bool
isJust a = case a of
  Just a => True
  _ => False

bindVar : String -> Ty -> TI Subst
bindVar name (TyVar r n) = empty
bindVar name other =
  if isJust $ find (== name) (freeVars other)
    then pure $ insert name other empty
    else pure $ insert name other empty

unify : Ty -> Ty -> TI Subst

unify (TyArrow r arg fun) (TyArrow r' arg' fun') = do
  fstSub <- unify arg arg'
  sndSub <- unify fun fun'
  pure (fstSub `composeSubst` sndSub)

unify (TyVar r u) t = bindVar u t
unify t (TyVar r u) = bindVar u t

unify (TySimple r a) (TySimple r' b) =
  if a == b
    then pure empty
    else lift $ throwE ("Cannot unify '" ++ a ++ "' and '" ++ b ++ "'")

unify a b = lift $ throwE ("Cannot unify '" ++ (show a) ++ "' and '" ++ (show b) ++ "'")

-- Inference

pathToList : Path -> List String
pathToList path =
  case path of
    (PDot r (MkName _ str) path) => (pathToList path)
    (PName r str)                => [str]

getVar : Context -> Path -> TI Ty
getVar ctx path =
  let listPath = pathToList path in
  case findVar listPath ctx of
    Just schema => do
      res <- instantiate schema
      pure res
    Nothing     => lift $ throwE ("Cannot find variable: '" ++ (show path) ++ "'")

getTy : Types -> Ty
getTy (TSimple r s)    = TySimple r s
getTy (TArrow r a b)   = TyArrow r (getTy a) (getTy b)
getTy (TPoly r path n) = TyPoly r path (getTy n)
getTy (TVar r a)       = TyVar r a

insertSchema : Context -> (List String) -> Schema -> Context
insertSchema ctx name schema = insert (remove ctx name) name schema

interface Infer e where
  infer : Context -> e -> TI (Subst, Ty)

Infer Literal where
  infer ctx (LStr r _) = pure (empty, TySimple r "Text")
  infer ctx (LInt r _) = pure (empty, TySimple r "Int" )

Infer Expr where
  infer ctx (ELit r lit) = infer ctx lit

  infer ctx (EId path) =
    let listPath = pathToList path in
    case findVar listPath ctx of
      Just schema => do
        res <- instantiate schema
        pure (empty, res)
      Nothing     => lift $ throwE ("Cannot find variable: '" ++ (show path) ++ "'")

  infer ctx (ELambda r (MkName r2 name) expr) = do
    ty <- newTyVar
    let ctxWithArg = insertSchema ctx [name] (MkSchema [] ty)
    (s1, t1) <- infer ctxWithArg expr
    pure (s1, TyArrow r (apply s1 ty) t1)

  infer ctx (ECall r fun expr) = do
    tyRes       <- newTyVar
    (s1, tyFun) <- infer ctx fun
    (s2, tyArg) <- infer (apply s1 ctx) expr
    s3          <- unify (apply s2 tyFun) (TyArrow r tyArg tyRes)
    pure ((s3 `composeSubst` s2) `composeSubst` s1, apply s3 tyRes)

  infer ctx (EDo r (exprs@(h :: tl))) = do
    let lastExpr    = last exprs
    let returnsVoid = init exprs
    (subst, ty) <- infer ctx lastExpr
    subTypeList <- traverse (infer ctx) returnsVoid
    substList   <- traverse (unify (TySimple emptyRange "Unit")) (map snd subTypeList)
    let voidSub   = foldl composeSubst empty substList
    let lastSub   = subTypeList
    pure (subst `composeSubst` voidSub, ty)

  infer ctx (EDo r []) = pure (empty, TySimple r "Unit")

  infer ctx (ELet r (MkName r2 name) val expr) = do
    (s0, tyVal)  <- infer ctx val
    let s0WithArg = insertSchema (apply s0 ctx) [name] (MkSchema [] tyVal)
    (s1, tyExpr) <- infer s0WithArg expr
    pure (s1, tyExpr)

  infer ctx e = lift $ throwE ("Not implemented")

Infer Def where
  infer ctx (Define name Nothing expr) = do
    (s1, ty) <- infer ctx expr
    pure (s1, ty)

  infer ctx (Define name (Just ret) expr) = do
    (s1, ty) <- infer ctx expr
    s2 <- unify (apply s1 (getTy ret)) ty
    pure (s2, ty)

public export
runInference : Def -> Either String (Subst, Ty)
runInference e =
  let ctx = (insert ["unit"] (MkSchema [] (TySimple emptyRange "Unit")) empty) in
  let (Id res) = runEitherT $ evalStateT 0 (infer (MkCtx ctx) e) in
  res


module PLFI.Part1.Isomorphism

import Data.Nat
import Data.Vect
import Syntax.PreorderReasoning
import Syntax.PreorderReasoning.Generic
import Control.Order
import Control.Relation

0
handwaving : {t : Type} -> (a : t) -> (b : t) -> (a = b)
handwaving a b = believe_me (the (a=a) Refl)

namespace Postulate

  export 0
  extensionality : {a,b : Type} -> {f,g : a -> b} -> ((x : a) -> f x = g x) -> f = g
  extensionality _ = handwaving f g

addX : (m,n : Nat) -> Nat
addX m Z = m
addX m (S n) = S (addX m n)

sameAdd : (m, n : Nat) -> addX m n = m + n
sameAdd m n = rewrite plusCommutative m n in helper m n
  where
    helper : (m, n : Nat) -> addX m n = n + m
    helper m 0     = Refl
    helper m (S n) = cong S (helper m n)

add : Nat -> Nat -> Nat
add = (+)

0
same : Isomorphism.addX = Isomorphism.add
same = extensionality (\p => extensionality (\q => sameAdd p q))

namespace Things

  interesting1 : (x : Nat) -> (y : Nat) -> Nat
  interesting1 x y = x + y

  interesting2 : Nat
  interesting2 = interesting1 {y=2} {x=1}

-- infix 0 _≃_
-- record _≃_ (A B : Set) : Set where
--   field
--     to   : A → B
--     from : B → A
--     from∘to : ∀ (x : A) → from (to x) ≡ x
--     to∘from : ∀ (y : B) → to (from y) ≡ y
-- open _≃_

public export
record Iso (a, b : Type) where
  constructor MkIso
  to     : a -> b
  from   : b -> a
  fromTo : (x : a) -> from (to x) === x
  toFrom : (y : b) -> to (from y) === y

-- interface IsoI a b where
--   toI   : a -> b
--   fromI : b -> a
--   fromToI : (x : a) -> fromI (toI x) = x
--   toFromI : (y : b) -> toI (fromI y) = y

-- ≃-refl : ∀ {A : Set}
--     -----
--   → A ≃ A
-- ≃-refl =
--   record
--     { to      = λ{x → x}
--     ; from    = λ{y → y}
--     ; from∘to = λ{x → refl}
--     ; to∘from = λ{y → refl}
--     }

public export
isoRefl : {a : Type} -> Iso a a
isoRefl = MkIso
  { to     = id
  , from   = id
  , fromTo = \x => Refl
  , toFrom = \x => Refl
  }

isoTo : Iso a b -> (a -> b)
isoTo i = i.to

-- ≃-sym : ∀ {A B : Set}
--   → A ≃ B
--     -----
--   → B ≃ A
-- ≃-sym A≃B =
--   record
--     { to      = from A≃B
--     ; from    = to   A≃B
--     ; from∘to = to∘from A≃B
--     ; to∘from = from∘to A≃B
--     }

public export
isoSym : Iso a b -> Iso b a
isoSym (MkIso to from fromTo toFrom) = MkIso from to toFrom fromTo

-- ≃-trans : ∀ {A B C : Set}
--   → A ≃ B
--   → B ≃ C
--     -----
--   → A ≃ C
-- ≃-trans A≃B B≃C =
--   record
--     { to       = to   B≃C ∘ to   A≃B
--     ; from     = from A≃B ∘ from B≃C
--     ; from∘to  = λ{x →
--         begin
--           (from A≃B ∘ from B≃C) ((to B≃C ∘ to A≃B) x)
--         ≡⟨⟩
--           from A≃B (from B≃C (to B≃C (to A≃B x)))
--         ≡⟨ cong (from A≃B) (from∘to B≃C (to A≃B x)) ⟩
--           from A≃B (to A≃B x)
--         ≡⟨ from∘to A≃B x ⟩
--           x
--         ∎}
--     ; to∘from = λ{y →
--         begin
--           (to B≃C ∘ to A≃B) ((from A≃B ∘ from B≃C) y)
--         ≡⟨⟩
--           to B≃C (to A≃B (from A≃B (from B≃C y)))
--         ≡⟨ cong (to B≃C) (to∘from A≃B (from B≃C y)) ⟩
--           to B≃C (from B≃C y)
--         ≡⟨ to∘from B≃C y ⟩
--           y
--         ∎}
--      }

public export
isoTrans : Iso a b -> Iso b c -> Iso a c
isoTrans ab bc = MkIso
  { to     = to bc . to ab
  , from   = from ab . from bc
  , fromTo = \x => Calc $
      |~ (from ab . from bc) ((to bc . to ab) x)
      -- ~~ (from ab . from bc) (to bc (to ab x))  ... (Refl)
      -- ~~ (from ab (from bc (to bc (to ab x))))  ... (Refl)
      ~~ (from ab (to ab x))                    ... (cong ab.from (bc.fromTo (ab.to x)))
      ~~ x                                      ... (ab.fromTo x)
  , toFrom = \y => rewrite ab.toFrom (bc.from y) in bc.toFrom y
--  , toFrom = \y => (?x (bc.toFrom y))
                    -- rewriteP (ab.toFrom (bc.from y)) (bc.toFrom y)

                    -- rewriteP : a = b -> P a -> P b
                    -- rewriteP Refl pa = pa
  }

-- module ≃-Reasoning where

--   infix  1 ≃-begin_
--   infixr 2 _≃⟨_⟩_
--   infix  3 _≃-∎

--   ≃-begin_ : ∀ {A B : Set}
--     → A ≃ B
--       -----
--     → A ≃ B
--   ≃-begin A≃B = A≃B

--   _≃⟨_⟩_ : ∀ (A : Set) {B C : Set}
--     → A ≃ B
--     → B ≃ C
--       -----
--     → A ≃ C
--   A ≃⟨ A≃B ⟩ B≃C = ≃-trans A≃B B≃C

--   _≃-∎ : ∀ (A : Set)
--       -----
--     → A ≃ A
--   A ≃-∎ = ≃-refl

-- open ≃-Reasoning

public export Reflexive  Type Iso where reflexive = isoRefl
public export Transitive Type Iso where transitive = isoTrans
public export Preorder   Type Iso where

public export
(~~) : (a, b : Type) -> Type
(~~) a b = Iso a b

-- 0
test : {a,b,c : Type} -> Iso a b -> Iso b c -> Iso a c
test ab bc = CalcWith {leq = Iso} $
  |~ a
  <~ b ... (ab)
  <~ c ... (bc)

-- [additive] Semigroup Nat where
--   a <+> b = ?h

-- [multiplicative] Semigroup Nat where
--   a <+> b = ?h2

-- infix 0 _≲_
-- record _≲_ (A B : Set) : Set where
--   field
--     to      : A → B
--     from    : B → A
--     from∘to : ∀ (x : A) → from (to x) ≡ x
-- open _≲_

-- infixr 100 <=

-- %hint -- Would be nice to have
public export
record (<=) (a, b : Type) where
  constructor MkEmb
  to     : a -> b
  from   : b -> a
  -- %hint : Would be nice to have
  fromTo : (x : a) -> from (to x) === x

%hint
fromToX : (ab : (a <= b)) -> (x : a) -> ab.from (ab.to x) === x
fromToX ab x = ab.fromTo x

-- %hint (.to)

-- (<=) : (a,b : Type) -> Type
-- (<=) a b = Emb a b

-- -- A deep question from the audience. :)
-- embIso : {a,b : Type} -> a <= b -> b <= a -> (Iso a b)
-- embIso = ?xembIso

embRefl : {a : Type} -> a <= a
embRefl = MkEmb Prelude.id Prelude.id (\x => Refl)

-- ≲-trans : ∀ {A B C : Set} → A ≲ B → B ≲ C → A ≲ C
-- ≲-trans A≲B B≲C =
--   record
--     { to      = λ{x → to   B≲C (to   A≲B x)}
--     ; from    = λ{y → from A≲B (from B≲C y)}
--     ; from∘to = λ{x →
--         begin
--           from A≲B (from B≲C (to B≲C (to A≲B x)))
--         ≡⟨ cong (from A≲B) (from∘to B≲C (to A≲B x)) ⟩
--           from A≲B (to A≲B x)
--         ≡⟨ from∘to A≲B x ⟩
--           x
--         ∎}
--      }

embTrans : {a,b,c : Type} -> a <= b -> b <= c -> a <= c
embTrans ab bc = MkEmb
  { to   = (bc.to . ab.to)
  , from = (ab.from . bc.from)
  , fromTo = \x => Calc $
      |~ ab.from (bc.from (bc.to (ab.to x)))
      ~~ ab.from (ab.to x)
          ... (cong ab.from (bc.fromTo (ab.to x)))
      ~~ x
          ... (ab.fromTo x)
--          ... (fromToX ab x)
  }

-- ≲-antisym : ∀ {A B : Set}
--   → (A≲B : A ≲ B)
--   → (B≲A : B ≲ A)
--   → (to A≲B ≡ from B≲A)
--   → (from A≲B ≡ to B≲A)
--     -------------------
--   → A ≃ B
-- ≲-antisym A≲B B≲A to≡from from≡to =
--   record
--     { to      = to A≲B
--     ; from    = from A≲B
--     ; from∘to = from∘to A≲B
--     ; to∘from = λ{y →
--         begin
--           to A≲B (from A≲B y)
--         ≡⟨ cong (to A≲B) (cong-app from≡to y) ⟩
--           to A≲B (to B≲A y)
--         ≡⟨ cong-app to≡from (to B≲A y) ⟩
--           from B≲A (to B≲A y)
--         ≡⟨ from∘to B≲A y ⟩
--           y
--         ∎}
--     }

embAntisym
  :  {a,b : Type}
  -> (ab : (a <= b)) -> (ba : (b <= a))
  -> (to ab = from ba) -> (from ab = to ba)
  -> Iso a b
embAntisym (MkEmb t f ft) (MkEmb _ _ ft1) Refl Refl = MkIso t f ft ft1

Reflexive  Type (<=) where reflexive = embRefl
Transitive Type (<=) where transitive = embTrans
Preorder   Type (<=) where

embFromIso : {a,b : Type} -> Iso a b -> (a <= b)
-- Issue in internel meta variable
-- embFromIso (MkIso to from fromTo toFrom) = MkEmb (\{arg:2022} => to arg) from fromTo
embFromIso (MkIso to from fromTo toFrom) = MkEmb to from fromTo


module Data.Crypto.Encryption.Block.EncryptionMode

import Data.Bits

import Data.Crypto.Encryption
import Data.Crypto.Encryption.Block
import Data.Crypto.Encryption.Stream
import Data.Crypto.Util

%default total
%access public export

data ElectronicCookbook : Nat -> Type where
  ECB : ElectronicCookbook n

-- This is ECB (Electronic Cookbook) - no initialization vector
-- ECB should be considered insecure regardless of the cipher used
implementation EncryptionMode ElectronicCookbook where
  encryptBlocks key _ blocks = map (encryptBlock key) blocks
  decryptBlocks key _ blocks = map (decryptBlock key) blocks

data CipherBlockChainingMode : Nat -> Type where
  CBC : Bits n -> CipherBlockChainingMode n

implementation EncryptionMode CipherBlockChainingMode where
  encryptBlocks _ _ [] = []
  encryptBlocks key (CBC iv) (plain::rest) =
    let ciph = encryptBlock key (plain `xor` iv)
    in ciph :: encryptBlocks key (CBC ciph) rest
  decryptBlocks _ _ [] = []
  decryptBlocks key (CBC iv) (ciph::rest) =
    (decryptBlock key ciph `xor` iv) :: decryptBlocks key (CBC ciph) rest

data PropagatingCipherBlockChainingMode : Nat -> Type where
  PCBC : Bits n -> PropagatingCipherBlockChainingMode n

implementation EncryptionMode PropagatingCipherBlockChainingMode where
  encryptBlocks _ _ [] = []
  encryptBlocks key (PCBC iv) (plain::rest) =
    let ciph = encryptBlock key (plain `xor` iv)
    in ciph :: encryptBlocks key (PCBC (plain `xor` ciph)) rest
  decryptBlocks _ _ [] = []
  decryptBlocks key (PCBC iv) (ciph::rest) =
    let plain = decryptBlock key ciph `xor` iv
    in plain :: decryptBlocks key (PCBC (plain `xor` ciph)) rest

data CipherFeedbackMode : Nat -> Type where
  CFB : Bits n -> CipherFeedbackMode n

implementation EncryptionMode CipherFeedbackMode where
  encryptBlocks _ _ [] = []
  encryptBlocks key (CFB iv) (plain::rest) =
    let ciph = encryptBlock key iv `xor` plain
    in ciph :: encryptBlocks key (PCBC ciph) rest
  decryptBlocks _ _ [] = []
  decryptBlocks key (CFB iv) (ciph::rest) =
    (decryptBlock key iv `xor` ciph) :: decryptBlocks key (PCBC ciph) rest

data OutputFeedbackMode : Nat -> Type where
  OFB : Bits n -> OutputFeedbackMode n

implementation EncryptionMode OutputFeedbackMode where
  encryptBlocks _ _ [] = []
  encryptBlocks key (OFB iv) (plain::rest) =
    let newIV = encryptBlock key iv
    in (plain `xor` newIV) :: encryptBlocks key (OFB newIV) rest
  decryptBlocks _ _ [] = []
  decryptBlocks key (OFB iv) (ciph::rest) =
    let newIV = decryptBlock key iv
    in (ciph `xor` newIV) :: decryptBlocks key (OFB newIV) rest

||| `OutputFeedbackMode` allows any `BlockCipher` to be treated as a
||| `StreamCipher`.
implementation BlockCipher b bitsPerBlock _ => StreamCipher (b, OutputFeedbackMode bitsPerBlock) bitsPerBlock where
  generateKeystream (b, OFB iv) =
    let newIV = encryptBlock b iv
    in newIV :: (generateKeystream (b, OFB newIV))

||| Counter mode takes a nonce, an initial “counter” value, and a function to
||| get the next counter value.
data CounterMode : Nat -> Type where
  CTR : Bits m -> Bits n -> (Bits n -> Bits n) -> CounterMode (m + n)

||| The most common counter mode starts at 0 and moves sequentially through the
||| natural numbers.
incrementalCTR : Bits m -> CounterMode (m + n)
incrementalCTR m = CTR m (intToBits 0) (plus (intToBits 1))

implementation EncryptionMode CounterMode where
  encryptBlocks _ _ [] = []
  encryptBlocks key (CTR nonce counter f) (plain::rest) =
    (plain `xor` encryptBlock key (append nonce counter))
      :: encryptBlocks key (CTR nonce (f counter) f) rest
  decryptBlocks _ _ [] = []
  decryptBlocks key (CTR nonce counter f) (ciph::rest) =
    (ciph `xor` decryptBlock key (append nonce counter))
      :: decryptBlocks key (CTR nonce (f counter) f) rest

||| `CounterMode` allows any `BlockCipher` to be treated as a `StreamCipher`.
implementation BlockCipher b bitsPerBlock _ => StreamCipher (b, CounterMode bitsPerBlock) bitsPerBlock where
  generateKeystream (b, CTR nonce counter f) =
    encryptBlock b (append nonce counter)
      :: generateKeystream (b, CTR nonce (f counter) f)


-- BearSSL's "adding holes" algorithm for XOR multiplication
-- Based on BearSSL ghash_ctmul64.c

module Crypto.Hash.GHash

import Utils.Bytes
import Data.Bits
import Data.List
import Data.Vect
import Data.DPair
import Data.Fin
import Data.Fin.Extra
import Data.Nat
import Data.Nat.Order.Properties
import Utils.Misc
import Crypto.Hash

HValues : Type
HValues = (Bits64, Bits64, Bits64, Bits64, Bits64, Bits64)

-- Carryless multiplication with "holes"
bmul : Bits64 -> Bits64 -> Bits64
bmul x y =
  let x0 = x .&. 0x1111111111111111
      x1 = x .&. 0x2222222222222222
      x2 = x .&. 0x4444444444444444
      x3 = x .&. 0x8888888888888888
      y0 = y .&. 0x1111111111111111
      y1 = y .&. 0x2222222222222222
      y2 = y .&. 0x4444444444444444
      y3 = y .&. 0x8888888888888888
      z0 = (x0 * y0) `xor` (x1 * y3) `xor` (x2 * y2) `xor` (x3 * y1)
      z1 = (x0 * y1) `xor` (x1 * y0) `xor` (x2 * y3) `xor` (x3 * y2)
      z2 = (x0 * y2) `xor` (x1 * y1) `xor` (x2 * y0) `xor` (x3 * y3)
      z3 = (x0 * y3) `xor` (x1 * y2) `xor` (x2 * y1) `xor` (x3 * y0)
  in (z0 .&. 0x1111111111111111) .|.
     (z1 .&. 0x2222222222222222) .|.
     (z2 .&. 0x4444444444444444) .|.
     (z3 .&. 0x8888888888888888)

rms : Bits64 -> Index {a=Bits64} -> Bits64 -> Bits64
rms m s x = (shiftL (x .&. m) s) .|. ((shiftR x s) .&. m)

rev64 : Bits64 -> Bits64
rev64 x =
  let x' =
      (rms 0x0000FFFF0000FFFF 16) $
      (rms 0x00FF00FF00FF00FF 8 ) $
      (rms 0x0F0F0F0F0F0F0F0F 4 ) $
      (rms 0x3333333333333333 2 ) $
      (rms 0x5555555555555555 1 ) x
  in (shiftL x' 32) .|. (shiftR x' 32)

gmult_core : Bits64 -> Bits64 -> HValues -> (Bits64, Bits64)
gmult_core y1 y0 (h0, h0r, h1, h1r, h2, h2r) =
  let y1r = rev64 y1
      y0r = rev64 y0
      y2 = xor y0 y1
      y2r = xor y0r y1r
      -- Karatsuba decomposition
      -- Here we decompose the 128 bit multiplication into
      -- 3 64-bits multiplication
      -- The h-suffixed variables are just multiplication for
      -- reversed bits, which is necessary because we want the
      -- high bits
      z0 = bmul y0 h0
      z1 = bmul y1 h1
      z2 = bmul y2 h2
      z0h = bmul y0r h0r
      z1h = bmul y1r h1r
      z2h = bmul y2r h2r
      z2  = xor (xor z0 z1) z2
      z2h = xor (xor z0h z1h) z2h
      z0h = shiftR (rev64 z0h) 1
      z1h = shiftR (rev64 z1h) 1
      z2h = shiftR (rev64 z2h) 1
      -- Since the operation is done in big-endian, but GHASH spec
      -- needs small endian, we flip the bits
      v0 = z0
      v1 = xor z0h z2
      v2 = xor z1 z2h
      v3 = z1h
      v3 = (shiftL v3 1) .|. (shiftR v2 63)
      v2 = (shiftL v2 1) .|. (shiftR v1 63)
      v1 = (shiftL v1 1) .|. (shiftR v0 63)
      v0 = (shiftL v0 1)
      -- Modular reduction to GF[128]
      v2 = v2 `xor` v0              `xor` (shiftR v0 1)  `xor` (shiftR v0 2) `xor` (shiftR v0 7)
      v1 = v1 `xor` (shiftL v0 63) `xor` (shiftL v0 62) `xor` (shiftL v0 57)
      v3 = v3 `xor` v1             `xor` (shiftR v1 1)  `xor` (shiftR v1 2) `xor` (shiftR v1 7)
      v2 = v2 `xor` (shiftL v1 63) `xor` (shiftL v1 62) `xor` (shiftL v1 57)
  in (v3, v2)

gcm_mult : HValues -> Vect 16 Bits8 -> Vect 16 Bits8
gcm_mult h y =
  let y1 = from_be $ take 8 y
      y0 = from_be $ drop 8 y
      (y1', y0') = gmult_core y1 y0 h
  in to_be {n=8} y1' ++ to_be y0'

mk_h_values : Vect 16 Bits8 -> HValues
mk_h_values h =
  let h1  = from_be $ take 8 h
      h0  = from_be $ drop 8 h
      h0r = rev64 h0
      h1r = rev64 h1
      h2  = xor h0 h1
      h2r = xor h0r h1r
  in (h0, h0r, h1, h1r, h2, h2r)

export
data GHash : Type where
  MkGHash : List Bits8 -> HValues -> Vect 16 Bits8 -> GHash

hash_until_done : GHash -> GHash
hash_until_done ghash@(MkGHash buffer hval state) =
  case splitAtExact 16 buffer of
    Just (a, b) => hash_until_done $ MkGHash b hval (gcm_mult hval (zipWith xor a state))
    Nothing => ghash

export
Digest GHash where
  digest_nbyte = 16
  update message (MkGHash buffer hval state) =
    hash_until_done (MkGHash (buffer <+> message) hval state)
  finalize (MkGHash buffer hval state) =
    let (MkGHash _ _ state) = hash_until_done (MkGHash (pad_zero 16 buffer) hval state)
    in state

export
MAC (Vect 16 Bits8) GHash where
  initialize_mac key = MkGHash [] (mk_h_values key) (replicate _ 0)


twice : (a -> a) -> a -> a
twice f x = f (f x)

Shape : Type
rotate : Shape -> Shape

turn_around : Shape -> Shape
turn_around = twice rotate


{-

:doc Eq

interface Eq ty where
  (==) : ty -> ty -> Bool
  (/=) : ty -> ty -> Bool
-}

occurrences : Eq ty => (item : ty) -> (values : List ty) -> Nat
occurrences item [] = 0
occurrences item (value :: values) = case value == item of
                                          False => occurrences item values
                                          True => 1 + occurrences item values

data Matter = Solid | Liquid | Gas

Eq Matter where
    (==) Solid Solid = True
    (==) Liquid Liquid = True
    (==) Gas Gas = True
    (==) _ _ = False

-- occurrences Liquid [Solid, Liquid, Liquid, Gas]

[myEq] Eq Matter where
    (==) _ _ = True

-- occurrences @{myEq} Liquid [Solid, Liquid, Liquid, Gas]


module Backprop.Node

import Backprop.CanBack
import Backprop.MathUtils
import Control.Optics
import Data.Floating
import public Data.List.Quantifiers

%hide Data.Morphisms.Op

public export
Op : (args : List Type) -> (o : Type) -> Type
Op args o = HList args -> Pair o (o -> HList args)

export
data Node : (i : Type) -> (o : Type) -> Type where
  ||| the input itself
  Input : Node a a
  ||| an operation
  Oper : {args : _} -> All CanBack args => Op args o -> All (Node i) args -> Node i o

public export
interface Functor f => NodeDistributive f where
  collect : {o : _} -> CanBack o => f (Node i o) -> Node i (f o)

public export
interface Functor f => NodeTraversable f where
  sequence : {o : _} -> CanBack o => Node i (f o) -> f (Node i o)

public export
NodeFunctor : (Type -> Type) -> Type
NodeFunctor f = (NodeTraversable f, NodeDistributive f)

export
map : {a, b : _} -> (CanBack a, CanBack b, NodeFunctor f) => (Node i a -> Node i b) -> Node i (f a) -> Node i (f b)
map f = collect . map f . sequence

export
input : Node a a
input = Input

export
op1 : {a1 : _} -> CanBack a1 => Op [a1] o -> Node i a1 -> Node i o
op1 op x = Oper op [x]

export
op2 : {a1, a2 : _} -> CanBack a1 => CanBack a2 => Op [a1, a2] o -> Node i a1 -> Node i a2 -> Node i o
op2 op x y = Oper op [x, y]

||| lift a constant into a `Node`
export
const : a -> Node i a
const x = Oper (\[] => (x, const [])) []

export
FromDouble a => FromDouble (Node i a) where
  fromDouble = const . fromDouble

export
{a : _} -> (CanBack a, Num a) => Num (Node i a) where
  (+) = op2 $ \[x, y] => (x + y, \d => [d, d])
  (*) = op2 $ \[x, y] => (x * y, \d => [d * y, x * d])
  fromInteger = const . fromInteger

export
{a : _} -> (CanBack a, Neg a) => Neg (Node i a) where
  (-) = op2 $ \[x, y] => (x - y, \d => [d, -d])
  negate = op1 $ \[x] => (-x, \d => [-d])

export
{a : _} -> (CanBack a, Ord a, Neg a, Abs a) => Abs (Node i a) where
  abs = op1 $ \[x] => (abs x, \d => [d * signum_zero x])

export
{a : _} -> (CanBack a, Neg a, Fractional a) => Fractional (Node i a) where
  (/) = op2 $ \[x, y] => (x / y, \d => [d / y, -d * x / (y * y)])
  recip = op1 $ \[x] => (recip x, \d => [-d / (x * x)])

export
{a : _} -> (CanBack a, Neg a, Floating a, Fractional (Node i a)) => Floating (Node i a) where
  pi = const pi
  sqrt = op1 $ \[x] => (sqrt x, \d => [d / (2 * sqrt x)])
  exp = op1 $ \[x] => (exp x, \d => [d * exp x])
  ln = op1 $ \[x] => (ln x, \d => [d / x])
  log base x = ln x / ln base
  pow = op2 $ \[x, y] =>
    ( pow x y
    , \d => let k = d * pow x (y - 1) in [k * y, k * x * ln x]
    )
  sin = op1 $ \[x] => (sin x, \d => [d * cos x])
  cos = op1 $ \[x] => (cos x, \d => [d * -sin x])
  tan = op1 $ \[x] => (tan x, \d => let cos' = cos x in [d / (cos' * cos')])
  asin = op1 $ \[x] => (asin x, \d => [d / sqrt (1 - x * x)])
  acos = op1 $ \[x] => (acos x, \d => [-d / sqrt (1 - x * x)])
  atan = op1 $ \[x] => (atan x, \d => [d / (1 - x * x)])
  sinh = op1 $ \[x] => (sinh x, \d => [d * cosh x])
  cosh = op1 $ \[x] => (cosh x, \d => [d * sinh x])
  tanh = op1 $ \[x] => (tanh x, \d => let cosh' = cosh x in [d / (cosh' * cosh')])

mutual
  run_all : {as : _} -> {auto prf : All CanBack as} -> CanBack i => All (Node i) as -> i -> (HList as, All (\b => b -> i -> i) as)
  run_all [] input = ([], [])
  run_all {prf = prf :: prfs} (arg :: args) input =
    let (x, y) = run arg input in bimap (x ::) (y ::) (run_all {prf = prfs} args input)

  combine_all : (backs : All (\b => b -> i -> i) as) -> HList as -> i -> i
  combine_all [] [] = id
  combine_all (back :: backs) (x :: xs) = combine_all backs xs . back x

  export
  run : {o : _} -> {auto prf : CanBack i} -> CanBack o => Node i o -> i -> (o, o -> i -> i)
  run Input input = (input, \d => add @{prf} d)
  run (Oper op arg_nodes) input =
    let
      (args, backs) = run_all arg_nodes input
      (output, back) = op args
    in
      (output, combine_all backs . back)

||| output: (the value, the gradient of inputs)
export
backprop : {o : _} -> (CanBack i, CanBack o) => Node i o -> i -> (o, i)
backprop node input = let (y, back) = run node input in (y, back one zero)

||| `backprop` but discards output value
export
grad : {o : _} -> (CanBack i, CanBack o) => Node i o -> i -> i
grad node input = snd $ backprop node input

mutual
  eval_all : All (Node i) as -> i -> HList as
  eval_all [] input = []
  eval_all (x :: xs) input = eval x input :: eval_all xs input

  ||| evaluates a `Node`, more efficient than `run` as gradient calculations are skipped
  |||
  ||| input: inputs, output node
  ||| output: output value
  export
  eval : Node i o -> i -> o
  eval Input input = input
  eval (Oper op args) input = fst $ op (eval_all args input)


module Control.Algebra.ZZBezoutsIdentity

import Control.Algebra
import Classes.Verified

import Data.ZZ
import Control.Algebra.NumericInstances
import Control.Algebra.ZZVerifiedInstances
import Data.Matrix.AlgebraicVerified 	-- for (ringNeutralIsMultZeroL)

import Control.Algebra.ZZDivisors

import Data.Fin.FinOrdering
import Data.Fin.Structural

import Control.Isomorphism

-- Dependent pattern matching using (do) notation binds improves clarity
import Control.Monad.Identity
import Syntax.PreorderReasoning

import Control.Algebra.DiamondInstances

-- For example
import Data.ZZ.ZZModulo

{-
Table of contents:
* Lemmas for verifying the euclidean algorithm (bezoutsIdentityZZIfModulo)
* (bezoutsIdentityZZIfModulo) implementation
* (Commentary) "Goal: Separation of algorithm from verification."
* The GCD derived from (modZT), itself derived from (modNatT).
-}

{-
Lemmas for verifying the euclidean algorithm (bezoutsIdentityZZIfModulo)
-}

total
bezoutsIdentityLincombEqZZ :
	(a, b, c, d, q, r : ZZ)
	-> q<.>d = c <-> r
	-> b<.>c <+> (a <-> b <.> q)<.>d
		= a<.>d <+> b<.>r
bezoutsIdentityLincombEqZZ a b c d q r pr =
	(
	b<.>c <+> (a <-> b <.> q)<.>d
	) ={
		cong {f=((b<.>c) <+>)}
		$ (
		(a <-> b <.> q)<.>d
		) ={ ringOpIsDistributiveSubR _ _ _ }= (
		a<.>d <-> (b<.>q)<.>d
		) ={
			cong {f=((a<.>d) <->)}
			{- Bracketing style switch -}
			$ ( b<.>q<.>d ) ={ sym $ ringOpIsAssociative b q d }=
			( b<.>(q<.>d) ) ={ cong {f=(b<.>)} pr }=
			( b<.>(c <-> r) ) QED
			}= (
		a<.>d <-> b<.>(c <-> r)
		) ={ abelianGroupOpIsCommutative (a <.> d) _ }= (
		(inverse $ b<.>(c <-> r)) <+> a <.> d
		) QED
		}= (
	b <.> c <+> ( (inverse $ b<.>(c <-> r)) <+> a <.> d )
	) ={ semigroupOpIsAssociative _ _ _ }= (
	b <.> c <-> b<.>(c <-> r) <+> a <.> d
	) ={ abelianGroupOpIsCommutative _ (a <.> d) }= (
	a <.> d <+> ( b <.> c <-> b<.>(c <-> r) )
	) ={
		cong {f=((a <.> d) <+>)}
		$ trans (sym $ ringOpIsDistributiveSubL _ _ _)
		$ cong {f=(b<.>)}
		{- Bracketing style switch -}
		$ ( c <-> (c <-> r) )
			={
				cong {f=(c<+>)}
				$ trans (ringOpIsDistributiveR _ _ _)
				$ cong {f=((inverse c) <+>)}
				$ inverseIsInvolution r
				}=
		( c <+> (inverse c <+> r) )
			={ semigroupOpIsAssociative c (inverse c) r }=
		( (c <-> c) <+> r )
			={ cong {f=(<+> r)} $ groupInverseIsInverseL c }=
		( Algebra.neutral <+> r )
			={ monoidNeutralIsNeutralR _ }=
		( r ) QED
		}= (
	a<.>d <+> b<.>r
	) QED

total
bezoutsIdentityExtQuotientLincombZZ :
	(c, d, q, r, x : ZZ)
	-> (q<.>d <+> r = c)
	-> (d `quotientOverZZ` x)
	-> (r `quotientOverZZ` x)
	-> (c `quotientOverZZ` x)
bezoutsIdentityExtQuotientLincombZZ c d q r x eqpr (dq ** dqPr) (rq ** rqPr)
	= (q<.>dq <+> rq
	** trans (ringOpIsDistributiveR (q<.>dq) rq x)
	$ rewrite rqPr
	in trans ( cong { f= (<+>r) }
		$ trans (sym $ ringOpIsAssociative _ _ _)
		$ cong {f=(q<.>)} $ dqPr )
	$ eqpr)

total
bezQTy : (c, d, a, b : ZZ) -> Type
bezQTy c d a b =
	( c `quotientOverZZ` (a<.>c <+> b<.>d)
	, d `quotientOverZZ` (a<.>c <+> b<.>d) )

{-
(bezoutsIdentityZZIfModulo) implementation

Given a modulo operator giving the remainder in a remainder-division pair,
produce a linear combination of two input integers which divides them both.

Since every number dividing both integers divides every linear combination
of them, this gives the greatest common denominator.

The latter statement isn't proved here, though.
-}

{- (bezoutsIdentityZZIfModulo) parameters -}
parameters (
	{-
	The nonnegative residue of the 1st modulo the subgroup generated by the 2nd.
	Can factor through a modulo on naturals, but must be everywhere defined.
	-}
	modZ : ZZ -> ZZ -> ZZ
	, quotientPartZ : (x, m : ZZ) -> (x <-> modZ x m) `quotientOverZZ` m
	) {

bezoutsIdentityZZIfModulo :
	( c, d : ZZ )
	-> ( zpar : (ZZ, ZZ) ** uncurry (bezQTy c d) zpar )
-- bezoutsIdentityZZIfModulo c (Pos Z) = ( (1, 0) ** ((1 ** _), (0 ** _)) )
bezoutsIdentityZZIfModulo c (Pos Z) = ( (Algebra.unity, Algebra.neutral)
	** rewrite ringWithUnityIsUnityR c
		in rewrite monoidNeutralIsNeutralL c
		in ( (Algebra.unity ** ringWithUnityIsUnityR c)
			, (Algebra.neutral ** ringNeutralIsMultZeroL c) ) )
bezoutsIdentityZZIfModulo c d with (bezoutsIdentityZZIfModulo d (c `modZ` d))
	| ((a,b) ** oldpr) = runIdentity $ do {
			(q ** qIsDivD) <- Id $ c `quotientPartZ` d
			((a' ** divsPrA), (b' ** divsPrB)) <- Id $ oldpr
			-- reverifyDivZ across the bind
			let reverifyDivZ' =
				trans (cong {f=(<+> (c `modZ` d))} qIsDivD)
				$ groupSubtractionIsRDivision c $ c `modZ` d
			-- the goal linear combo of c & d gives d `gcd` (c `modZ` d)
			let lincombEq = bezoutsIdentityLincombEqZZ
				a b c d q (c `modZ` d)
				$ groupDivisionAddLToSubR
					(q <.> d) (c `modZ` d) c
					$ reverifyDivZ'
			-- d `gcd` (c `modZ` d) | c
			let extQuotientLincombZZ = bezoutsIdentityExtQuotientLincombZZ
				c d q (c `modZ` d) ( a<.>d <+> b<.>(c `modZ` d) )
				reverifyDivZ'
				(a' ** divsPrA)
				(b' ** divsPrB)
			return $ ( (b, a <-> b <.> q)
				** ((getWitness extQuotientLincombZZ
					** trans (cong lincombEq)
					$ getProof extQuotientLincombZZ)
				, (a' ** trans (cong lincombEq) divsPrA)) )
		}

} {- (bezoutsIdentityZZIfModulo) parameters -}

{-
Goal: Separation of algorithm from verification.
Obstructions: (Problem 1), (Problem 2).

divZ : ZZ -> ZZ -> ZZ
divZ a b = getWitness $ quotientPartZ a b

reverifyDivZ : divZ a b <.> b <+> modZ a b = a
reverifyDivZ {a} {b}
	= trans (cong {f=(<+> (a `modZ` b))}
		$ getProof $ quotientPartZ a b)
	$ groupSubtractionIsRDivision a $ a `modZ` b

||| Greatest Common Denominator (GCD) linear combination factors
euclideanAlgPar : ZZ -> ZZ -> (ZZ, ZZ)
euclideanAlgPar c (Pos Z) = (1, 0)
euclideanAlgPar c d = (b, a <-> b <.> q)
	where
		eucParReduced : (ZZ, ZZ)
		eucParReduced = euclideanAlgPar d (c `modZ` d)
		a : ZZ
		a = fst eucParReduced
		b : ZZ
		b = snd eucParReduced
		q : ZZ
		q = c `divZ` d

||| Greatest Common Denominator (GCD)
euclideanAlg : ZZ -> ZZ -> ZZ
euclideanAlg c d = let (a, b) = euclideanAlgPar c d in a<.>c <+> b<.>d

bezoutsIdentity : (c, d : ZZ) -> uncurry (bezQTy c d) $ euclideanAlgPar c d

---
Problem 1)
rewrite did not change type uncurry (bezQTy c (Pos 0)) (euclideanAlgPar c (Pos 0))
---

bezoutsIdentity c (Pos Z)
	= rewrite ringWithUnityIsUnityR c
	in rewrite monoidNeutralIsNeutralL c
	in ( (Algebra.unity ** ringWithUnityIsUnityR c)
		, (Algebra.neutral ** ringNeutralIsMultZeroL c) )

---
Problem 2)
Proof must be repeated for positive and negative halves,
since otherwise the case can't be determined to not be the one for 2nd argument 0.
---

bezoutsIdentity c d@(Pos $ S d') = ?bezoutsIdentity_rhs_2
bezoutsIdentity c d@(NegS d') = ?bezoutsIdentity_rhs_3

-}

{-
The GCD derived from (modZT), itself derived from (modNatT).
-}

bezoutZT :
	( c, d : ZZ )
	-> ( zpar : (ZZ, ZZ) ** uncurry (bezQTy c d) zpar )
bezoutZT = bezoutsIdentityZZIfModulo modZT quotientPartModZT


module Data.Fin.Structural

import Data.Fin
import Data.ZZ
import Control.Isomorphism

import Data.Fin.FinOrdering

%default total

{-
Structure of (Fin)s
* in general
* in terms of ordering
-}

ltePredLeft : LTE (S n) m -> LTE n m
ltePredLeft = fromLteSucc . lteSuccRight

gtnatFZImpliesIsFinSucc :
	(nel : Fin (S nu))
	-> (LTRel Z $ finToNat nel)
	-> (prednel : Fin nu ** nel = FS prednel)
gtnatFZImpliesIsFinSucc FZ ltpr = void $ succNotLTEzero ltpr
gtnatFZImpliesIsFinSucc (FS prednel) ltpr = (prednel ** Refl)

trichotomy : (n,m : Nat) -> Either (n `LT` m) $ Either (n = m) (m `LT` n)
trichotomy Z Z = Right $ Left Refl
trichotomy (S predn) Z = Right $ Right $ zLtSuccIsTrue predn
trichotomy Z (S predm) = Left $ zLtSuccIsTrue predm
trichotomy (S predn) (S predm)
	= either ( Left . LTESucc )
		( Right . either (Left . cong) (Right . LTESucc) )
	$ trichotomy predn predm

||| See (trichotomy)
eqOrGTToLT :
	Either (n = m) (LT m n)
	-> LTE m n
eqOrGTToLT = lteRelToLTE . either (Right . sym) Left

||| Allows: under decEq : (-) {smaller=notLTToGTE notlt}
notLTToGTE : Not (LT a b) -> LTE b a
notLTToGTE {a} {b} nonpr = eqOrGTToLT
	$ either (void . nonpr) id
	$ trichotomy a b

total
FinSZAreFZ : (x : Fin 1) -> x=FZ
FinSZAreFZ FZ = Refl
FinSZAreFZ (FS voda) = FinZElim voda

commuteFSWeaken : (i : Fin n) -> (FS $ weaken i = weaken $ FS i)
commuteFSWeaken i = Refl

{-
"
LTE (S (finToNat last)) (finToNat i)  cannot be a parameter of Prelude.Uninhabited.Uninhabited
(Type class arguments must be injective)
"

> instance Uninhabited (LTE (S $ finToNat $ last {n=n}) (finToNat $ the (Fin (S n)) i)) where {
> 	uninhabited = ?notSNatLastLTEAnything
> }
-}

-- notSNatLastLTEAnything : LTE (S $ finToNat $ last {n=n}) (finToNat $ the (Fin (S n)) i) -> Void

total
notSNatLastLTEAnything : {n : Nat} -> {i : Fin (S n)} -> LTE (S $ finToNat $ last {n=n}) (finToNat i) -> Void
notSNatLastLTEAnything {n} {i=FZ} = uninhabited
notSNatLastLTEAnything {n=Z} {i=FS pri} = FinZElim pri
notSNatLastLTEAnything {n=S predn} {i=FS pri} = notSNatLastLTEAnything . fromLteSucc

finToNatWeaken : {k : Fin n} -> finToNat k = finToNat (weaken k)
finToNatWeaken {k=FZ} = Refl
finToNatWeaken {k=FS k} = cong {f=S} $ finToNatWeaken {k}

partitionNatWknLT : (pnel : Fin n) -> (el : Fin (S n)) -> (elDown : LTRel (finToNat $ weaken pnel) (finToNat el)) -> Either ( LTRel (finToNat $ FS pnel) (finToNat el) ) ( el=FS pnel )
partitionNatWknLT pnel el elDown = map (sym . (finToNatInjective (FS pnel) el)) $ lteToLTERel $ lteFromEqLeft (cong {f=S} $ sym finToNatWeaken) elDown
	where
		lteFromEqLeft : (a=b) -> LTE a c -> LTE b c
		lteFromEqLeft pr lel = rewrite (sym pr) in lel

total
splitFinS : (i : Fin (S predn)) -> Either ( k : Fin predn ** i = weaken k ) ( i = Data.Fin.last )
splitFinS {predn=Z} FZ = Right Refl
splitFinS {predn=Z} (FS j) = absurd j
splitFinS {predn=S prededn} FZ = Left ( FZ ** Refl )
splitFinS {predn=S prededn} (FS prednel) with ( splitFinS prednel )
	| Left ( k ** pr ) = Left ( FS k ** cong pr )
	| Right pr = Right $ cong pr

wknLTLast : (a : Fin n) -> LT (finToNat $ weaken a) (finToNat $ last {n=n})
wknLTLast FZ = zLtSuccIsTrue _
wknLTLast (FS k) = LTESucc $ wknLTLast k

ltenatLastIsTrue2 : (i : Fin (S nu)) -> LTERel (finToNat i) $ finToNat $ last {n=nu}
ltenatLastIsTrue2 FZ {nu=Z} = Right Refl
ltenatLastIsTrue2 (FS k) {nu=Z} = absurd k
ltenatLastIsTrue2 i {nu=S prednu} with (splitFinS i)
	| Left (k ** prwkn) = Left $ rewrite prwkn in wknLTLast {n=S prednu} k
	| Right prLast = Right $ cong {f=finToNat} prLast

{- (ltenatLastIsTrue) subsection -}

notLTSelf : Not (LT a a)
notLTSelf {a=Z} = succNotLTEzero
notLTSelf {a=S preda} = notLTSelf . fromLteSucc

lteUnique : {a, b : Nat} -> (x, y : LTERel a b) -> x = y
lteUnique (Left LTEZero) (Left LTEZero) impossible
lteUnique (Left LTEZero) (Left (LTESucc ltY)) impossible
lteUnique (Left (LTESucc ltX)) (Left LTEZero) impossible
lteUnique (Left (LTESucc ltX)) (Left (LTESucc ltY)) {b=Z} impossible
lteUnique (Left (LTESucc LTEZero)) (Left (LTESucc LTEZero)) {a=Z} {b=S right} = Refl
lteUnique (Left (LTESucc ltX)) (Left (LTESucc ltY)) {a=S left} {b=S right}
	with (lteUnique (Left ltX) (Left ltY))
	| prEq = cong {f=Left . LTESucc} $ leftInjective prEq
lteUnique (Left ltX) (Right prEq) {b}
	= void $ notLTSelf $ replace prEq {P=\x => LT x b} ltX
lteUnique (Right prEq) (Left ltY) {b}
	= void $ notLTSelf $ replace prEq {P=\x => LT x b} ltY
lteUnique (Right Refl) (Right Refl) = Refl
lteUnique (Right Refl) (Right Refl) {a=Z} {b = S right} impossible
lteUnique (Right Refl) (Right Refl) {a = S left} {b=Z} impossible

ltenatLastIsTrue : Iso (nel : Fin (S nu) ** LTERel (finToNat nel) $ finToNat $ last {n=nu}) $ Fin (S nu)
ltenatLastIsTrue = MkIso
	getWitness
	(\nel => (nel ** ltenatLastIsTrue2 nel))
	(\y => Refl)
	ltenatLastIsTrue_fromTo
	where
		ltenatLastIsTrue_fromTo :
			(x : (nel : Fin (S nu)
				** LTERel (finToNat nel) $ finToNat $ last {n=nu}))
			-> (getWitness x ** ltenatLastIsTrue2 (getWitness x)) = x
		ltenatLastIsTrue_fromTo (nel ** ltepr)
			= rewrite sameLTEPr in Refl
			where
				sameLTEPr : ltenatLastIsTrue2 nel = ltepr
				sameLTEPr = lteUnique (ltenatLastIsTrue2 nel) ltepr

{-
Nat algebraic theorems
-}

lteUnsumLeftSummandRight : (x `plus` y) `LT` z -> y `LT` z
lteUnsumLeftSummandRight {x=Z} = id
lteUnsumLeftSummandRight {x=S predx} = lteUnsumLeftSummandRight . ltePredLeft

lteUnsumLeftSummandLeft : (x `plus` y) `LT` z -> x `LT` z
lteUnsumLeftSummandLeft {x} {y} = rewrite (plusCommutative x y)
	in lteUnsumLeftSummandRight

ltePlus : (a, b : Nat) -> b `LTE` (a `plus` b)
ltePlus a Z = LTEZero
ltePlus a (S predb) = rewrite sym $ plusSuccRightSucc a predb in LTESucc $ ltePlus a predb

lteImplSuccMinusPreserved : (a, b : Nat)
	-> b `LTE` a
	-> (S a) `minus` b = S (a `minus` b)
lteImplSuccMinusPreserved a Z _ = cong $ sym $ minusZeroRight a
lteImplSuccMinusPreserved Z (S predb) lteBA = void $ succNotLTEzero lteBA
lteImplSuccMinusPreserved (S preda) (S predb) lteBA
	= lteImplSuccMinusPreserved preda predb $ fromLteSucc lteBA

natPlusInvertibleR : (a, b : Nat) -> (a `plus` b) `minus` b = a
natPlusInvertibleR a Z = trans (cong $ plusZeroRightNeutral a) $ minusZeroRight a
natPlusInvertibleR Z b = sym $ minusZeroN b
natPlusInvertibleR (S preda) (S predb)
	= trans (cong {f=\t => t `minus` predb} $ sym $ plusSuccRightSucc preda predb)
	$ trans (lteImplSuccMinusPreserved (plus preda predb) predb
		$ ltePlus preda predb)
	$ cong {f=S} $ natPlusInvertibleR preda predb

natPlusInvertibleL : (a, b : Nat) -> (b `plus` a) `minus` b = a
natPlusInvertibleL a b = rewrite plusCommutative b a in natPlusInvertibleR a b

lteToSumExpr :
	(x, m : Nat)
	-> m `LTE` x
	-> (k : Nat ** x = m `plus` k)
lteToSumExpr x Z lteMX = (x ** Refl)
lteToSumExpr Z (S predm) lteMX = void $ succNotLTEzero lteMX
lteToSumExpr (S predx) (S predm) lteMX
	= either
		(\ltMX =>
			let ltPMPX = fromLteSucc ltMX
			in let oldpr = lteToSumExpr predx (S predm) ltPMPX
			in (S $ getWitness oldpr
				** cong {f=S} $ trans (getProof oldpr)
				$ plusSuccRightSucc _ _))
		(\eqMX => (Z ** sym $ trans (cong $ plusZeroRightNeutral predm) eqMX))
	$ lteToLTERel lteMX


module Sub04Apply54x48

import ProofColDivSeqBase
import ProofColDivSeqPostulate

%default total
-- %language ElabReflection

-- 3(18x+16) --A[6,-4]->B[1,-2]--> 3(4x+3)
ab54x30To12x9 :
  (l:Nat) -> P (S (S (S (S (plus (plus (plus (plus (plus l l) l) (S (S (plus (plus l l) l))))
                                  (S (S (S (plus (plus (plus l l) l) (S (S (plus (plus l l) l))))))))
                            (S (S (S (plus (plus (plus l l) l) (S (S (plus (plus l l) l)))))))))))) 2
    -> P (S (S (S (plus (plus l l) (plus l l))))) 2
ab54x30To12x9 l prf =
  let prf2 = lvDown (S (S (S (S (plus (plus (plus (plus (plus l l) l) (S (S (plus (plus l l) l))))
                                  (S (S (S (plus (plus (plus l l) l) (S (S (plus (plus l l) l))))))))
                            (S (S (S (plus (plus (plus l l) l) (S (S (plus (plus l l) l)))))))))))) 2
                    prf in
    let prf3 = ab54x30To12x9' l prf2 in lvDown (S (S (S (plus (plus l l) (plus l l))))) 3 prf3

export
apply54x48 : P (S (S (S (S (plus (plus (plus (plus (plus l l) l) (S (S (plus (plus l l) l))))
                                  (S (S (S (plus (plus (plus l l) l) (S (S (plus (plus l l) l))))))))
                            (S (S (S (plus (plus (plus l l) l) (S (S (plus (plus l l) l)))))))))))) 2
  -> (m : Nat **
        (LTE (S m)
             (S (S (S (S (plus (plus (plus (plus (plus l l) l) (S (S (plus (plus l l) l)))) (S (S (S (plus (plus (plus l l) l) (S (S (plus (plus l l) l))))))))
                               (S (S (S (plus (plus (plus l l) l) (S (S (plus (plus l l) l)))))))))))),
         P m 2))
apply54x48 {l} col = let col2 = ab54x30To12x9 l col in ((S (S (S (plus (plus l l) (plus l l))))) ** (lte54x48 l, col2)) where
  lte54x48 : (l:Nat) -> LTE (S (S (S (S (plus (plus l l) (plus l l))))))
                            (S (S (S (S (plus (plus (plus (plus (plus l l) l) (S (S (plus (plus l l) l)))) (S (S (S (plus (plus (plus l l) l) (S (S (plus (plus l l) l))))))))
                                              (S (S (S (plus (plus (plus l l) l) (S (S (plus (plus l l) l))))))))))))
  lte54x48 Z = (lteSuccRight . lteSuccRight . lteSuccRight . lteSuccRight . lteSuccRight . lteSuccRight . lteSuccRight . lteSuccRight . lteSuccRight . lteSuccRight . lteSuccRight . lteSuccRight . LTESucc . LTESucc . LTESucc . LTESucc) LTEZero
  lte54x48 (S k) = let lemma = lte54x48 k in
    rewrite (sym (plusSuccRightSucc k k)) in
    rewrite (sym (plusSuccRightSucc (plus k k) (S (plus k k)))) in
    rewrite (sym (plusSuccRightSucc (plus k k) (plus k k))) in
    rewrite (sym (plusSuccRightSucc (plus k k) k)) in
    rewrite (sym (plusSuccRightSucc (plus (plus k k) k) (S (S (S (S (plus (plus k k) k))))))) in
    rewrite (sym (plusSuccRightSucc (plus (plus k k) k) (S (S (S (plus (plus k k) k)))))) in
    rewrite (sym (plusSuccRightSucc (plus (plus k k) k) (S (S (plus (plus k k) k))))) in
    rewrite (sym (plusSuccRightSucc (plus (plus (plus k k) k) (S (S (plus (plus k k) k))))  (S (S (S (S (S (S (S (S (plus (plus (plus k k) k) (S (S (plus (plus k k) k)))))))))))))) in
    rewrite (sym (plusSuccRightSucc (plus (plus (plus k k) k) (S (S (plus (plus k k) k))))  (S (S (S (S (S (S (S (plus (plus (plus k k) k) (S (S (plus (plus k k) k))))))))))))) in
    rewrite (sym (plusSuccRightSucc (plus (plus (plus k k) k) (S (S (plus (plus k k) k))))  (S (S (S (S (S (S (plus (plus (plus k k) k) (S (S (plus (plus k k) k)))))))))))) in
    rewrite (sym (plusSuccRightSucc (plus (plus (plus k k) k) (S (S (plus (plus k k) k))))  (S (S (S (S (S (plus (plus (plus k k) k) (S (S (plus (plus k k) k))))))))))) in
    rewrite (sym (plusSuccRightSucc (plus (plus (plus k k) k) (S (S (plus (plus k k) k))))  (S (S (S (S (plus (plus (plus k k) k) (S (S (plus (plus k k) k)))))))))) in
    rewrite (sym (plusSuccRightSucc (plus (plus (plus k k) k) (S (S (plus (plus k k) k))))  (S (S (S (plus (plus (plus k k) k) (S (S (plus (plus k k) k))))))))) in
    rewrite (sym (plusSuccRightSucc (plus (plus (plus (plus k k) k) (S (S (plus (plus k k) k)))) (S (S (S (plus (plus (plus k k) k) (S (S (plus (plus k k) k))))))))  (S (S (S (S (S (S (S (S (plus (plus (plus k k) k) (S (S (plus (plus k k) k)))))))))))))) in
    rewrite (sym (plusSuccRightSucc (plus (plus (plus (plus k k) k) (S (S (plus (plus k k) k)))) (S (S (S (plus (plus (plus k k) k) (S (S (plus (plus k k) k))))))))  (S (S (S (S (S (S (S (plus (plus (plus k k) k) (S (S (plus (plus k k) k))))))))))))) in
    rewrite (sym (plusSuccRightSucc (plus (plus (plus (plus k k) k) (S (S (plus (plus k k) k)))) (S (S (S (plus (plus (plus k k) k) (S (S (plus (plus k k) k))))))))  (S (S (S (S (S (S (plus (plus (plus k k) k) (S (S (plus (plus k k) k)))))))))))) in
    rewrite (sym (plusSuccRightSucc (plus (plus (plus (plus k k) k) (S (S (plus (plus k k) k)))) (S (S (S (plus (plus (plus k k) k) (S (S (plus (plus k k) k))))))))  (S (S (S (S (S (plus (plus (plus k k) k) (S (S (plus (plus k k) k))))))))))) in
    rewrite (sym (plusSuccRightSucc (plus (plus (plus (plus k k) k) (S (S (plus (plus k k) k)))) (S (S (S (plus (plus (plus k k) k) (S (S (plus (plus k k) k))))))))  (S (S (S (S (plus (plus (plus k k) k) (S (S (plus (plus k k) k)))))))))) in
    rewrite (sym (plusSuccRightSucc (plus (plus (plus (plus k k) k) (S (S (plus (plus k k) k)))) (S (S (S (plus (plus (plus k k) k) (S (S (plus (plus k k) k))))))))  (S (S (S (plus (plus (plus k k) k) (S (S (plus (plus k k) k))))))))) in
      (lteSuccRight . lteSuccRight . lteSuccRight . lteSuccRight . lteSuccRight . lteSuccRight . lteSuccRight . lteSuccRight . lteSuccRight . lteSuccRight . lteSuccRight . lteSuccRight . lteSuccRight . lteSuccRight . LTESucc . LTESucc . LTESucc . LTESucc) lemma


-- --------------------------------------------------------- [ Qualitative.idr ]
-- Module    : Qualitative.idr
-- Copyright : (c) Jan de Muijnck-Hughes
-- License   : see LICENSE
-- --------------------------------------------------------------------- [ EOH ]

||| Qualitative evaluations for
module GRL.Eval.Qualitative

import GRL.Common
import GRL.Model

%access public export

implementation [andSN] Cast SValue Nat where
  cast DENIED    = cast 0
  cast CONFLICT  = cast 1
  cast UNDECIDED = cast 1
  cast WEAKDEN   = cast 2
  cast NONE      = cast 3
  cast WEAKSATIS = cast 4
  cast SATISFIED = cast 5

implementation [andNS] Cast Nat SValue where
  cast Z                     = DENIED
  cast (S Z)                 = UNDECIDED
  cast (S (S Z))             = WEAKDEN
  cast (S (S (S Z)))         = NONE
  cast (S (S (S (S Z))))     = WEAKSATIS
  cast (S (S (S (S (S Z))))) = SATISFIED
  cast _                     = UNDECIDED

||| Calculate AND decomposition
getDecompAnd : List SValue -> SValue
getDecompAnd ss = cast @{andNS} $ foldl (\olds,s => min (toNat s) olds) (cast 5) ss
  where
    toNat : SValue -> Nat
    toNat sv = cast @{andSN} sv

implementation [orSN] Cast SValue Nat where
  cast DENIED    = cast 0
  cast WEAKDEN   = cast 1
  cast NONE      = cast 2
  cast WEAKSATIS = cast 3
  cast CONFLICT  = cast 4
  cast UNDECIDED = cast 4
  cast SATISFIED = cast 5

implementation [orNS] Cast Nat SValue where
  cast Z                     = DENIED
  cast (S Z)                 = WEAKDEN
  cast (S (S Z))             = NONE
  cast (S (S (S Z)))         = WEAKSATIS
  cast (S (S (S (S Z))))     = UNDECIDED
  cast (S (S (S (S (S Z))))) = SATISFIED
  cast _                     = UNDECIDED

||| Calculate IOR decomposition.
getDecompIOR : List SValue -> SValue
getDecompIOR ss = cast @{orNS} $ foldl (\olds,s => max (toNat s) olds) (cast 0) ss
  where
    toNat : SValue -> Nat
    toNat sv = cast @{orSN} sv

||| Calculate XOR decomposition.
getDecompXOR : List SValue -> SValue
getDecompXOR ss = cast @{orNS} $ foldl (\olds,s => min (toNat s) olds) (cast 5) ss
  where
    toNat : SValue -> Nat
    toNat sv = cast @{orSN} sv

||| Record to keep track of contribution valeu counts.
record ContribCount where
  constructor MkCCount
  noSatis  : Nat
  noWeakS  : Nat
  noWeakD  : Nat
  noDenied : Nat
  noUndec  : Nat

||| Default constructor for `ContribCount`
defCCount : ContribCount
defCCount = MkCCount Z Z Z Z Z

||| Implementation of `AdjustContributionCounters`
adJustCount : SValue -> ContribCount -> ContribCount
adJustCount SATISFIED cnt = record {noSatis  = (S (noSatis cnt))} cnt
adJustCount DENIED    cnt = record {noDenied = (S (noDenied cnt))} cnt
adJustCount WEAKSATIS cnt = record {noWeakS  = (S (noWeakS cnt))} cnt
adJustCount WEAKDEN   cnt = record {noWeakD  = (S (noWeakD cnt))} cnt
adJustCount UNDECIDED cnt = record {noUndec  = (S (noUndec cnt))} cnt
adJustCount _         cnt = cnt

||| Implementation of `AdjustContributionCounters` for a list of values.
adjustCounts : List SValue -> ContribCount
adjustCounts es = foldl (flip $ adJustCount) defCCount es

||| Implementation of the `WeightedContribution` look up table.
weightedContrib : SValue -> CValue -> SValue
weightedContrib x y = foldl (\res,(x',y',r') => if x == x' && y == y' then r' else res) NONE table
  where
    table : List (SValue, CValue, SValue)
    table = [ (DENIED   , MAKES  ,  DENIED   )
            , (DENIED   , HELPS  ,  WEAKDEN  )
            , (DENIED   , SOMEPOS,  WEAKDEN  )
            , (DENIED   , UNKNOWN,  NONE     )
            , (DENIED   , SOMENEG,  WEAKSATIS)
            , (DENIED   , HURTS  ,  WEAKSATIS)
            , (DENIED   , BREAK  ,  SATISFIED)
            , (WEAKDEN  , MAKES  ,  WEAKDEN  )
            , (WEAKDEN  , HELPS  ,  WEAKDEN  )
            , (WEAKDEN  , SOMEPOS,  WEAKDEN  )
            , (WEAKDEN  , UNKNOWN,  NONE     )
            , (WEAKDEN  , SOMENEG,  WEAKSATIS)
            , (WEAKDEN  , HURTS  ,  WEAKSATIS)
            , (WEAKDEN  , BREAK  ,  WEAKSATIS)
            , (WEAKSATIS, MAKES  ,  WEAKSATIS)
            , (WEAKSATIS, HELPS  ,  WEAKSATIS)
            , (WEAKSATIS, SOMEPOS,  WEAKSATIS)
            , (WEAKSATIS, UNKNOWN,  NONE     )
            , (WEAKSATIS, SOMENEG,  WEAKDEN  )
            , (WEAKSATIS, HURTS  ,  WEAKDEN  )
            , (WEAKSATIS, BREAK  ,  WEAKDEN  )
            , (SATISFIED, MAKES  ,  SATISFIED)
            , (SATISFIED, HELPS  ,  WEAKSATIS)
            , (SATISFIED, SOMEPOS,  WEAKSATIS)
            , (SATISFIED, UNKNOWN,  NONE     )
            , (SATISFIED, SOMENEG,  WEAKDEN  )
            , (SATISFIED, HURTS  ,  WEAKDEN  )
            , (SATISFIED, BREAK  ,  DENIED   )
            , (CONFLICT , MAKES  ,  UNDECIDED)
            , (CONFLICT , HELPS  ,  UNDECIDED)
            , (CONFLICT , SOMEPOS,  UNDECIDED)
            , (CONFLICT , UNKNOWN,  UNDECIDED)
            , (CONFLICT , SOMENEG,  UNDECIDED)
            , (CONFLICT , HURTS  ,  UNDECIDED)
            , (CONFLICT , BREAK  ,  UNDECIDED)
            , (UNDECIDED, MAKES  ,  UNDECIDED)
            , (UNDECIDED, HELPS  ,  UNDECIDED)
            , (UNDECIDED, SOMEPOS,  UNDECIDED)
            , (UNDECIDED, UNKNOWN,  UNDECIDED)
            , (UNDECIDED, SOMENEG,  UNDECIDED)
            , (UNDECIDED, HURTS  ,  UNDECIDED)
            , (UNDECIDED, BREAK  ,  UNDECIDED)
            , (NONE     , MAKES  ,  NONE     )
            , (NONE     , HELPS  ,  NONE     )
            , (NONE     , SOMEPOS,  NONE     )
            , (NONE     , UNKNOWN,  NONE     )
            , (NONE     , SOMENEG,  NONE     )
            , (NONE     , HURTS  ,  NONE     )
            , (NONE     , BREAK  ,  NONE     )]

||| Implementation of the `CompareSatisfiedAndDenied`  resolution
cmpSatAndDen : ContribCount -> SValue
cmpSatAndDen cnt =
  if noSatis cnt > 0 && noDenied cnt > 0
    then CONFLICT
    else if noSatis cnt > 0 && noDenied cnt == 0
      then SATISFIED
      else if noDenied cnt > 0 && noSatis cnt == 0
        then DENIED
        else if noSatis cnt == 0 && noDenied cnt == 0
          then NONE
          else NONE

||| Implementation of the `CompareWSandWD` function
cmpWSandWD : ContribCount -> SValue
cmpWSandWD cnt =
  if noWeakS cnt > noWeakD cnt
    then WEAKSATIS
    else if noWeakD cnt > noWeakS cnt
      then WEAKDEN
      else if noWeakD cnt == noWeakS cnt
        then NONE
        else NONE

||| Implementation of the `CombineContributionutions` function
combineContribs : ContribCount -> SValue
combineContribs c = doCombine (cmpWSandWD c) (cmpSatAndDen c)
  where
    table : List (SValue, SValue, SValue)
    table =[ (WEAKDEN  , DENIED   , DENIED   )
           , (WEAKDEN  , SATISFIED, WEAKSATIS)
           , (WEAKDEN  , CONFLICT , CONFLICT )
           , (WEAKDEN  , NONE     , WEAKDEN  )
           , (WEAKSATIS, DENIED   , WEAKDEN  )
           , (WEAKSATIS, SATISFIED, SATISFIED)
           , (WEAKSATIS, CONFLICT , CONFLICT )
           , (WEAKSATIS, NONE     , WEAKSATIS)
           , (NONE     , DENIED   , DENIED   )
           , (NONE     , SATISFIED, SATISFIED)
           , (NONE     , CONFLICT , CONFLICT )
           , (NONE     , NONE     , NONE     )]

    doCombine : SValue -> SValue -> SValue
    doCombine x y = foldl (\res,(x',y',r') => if x == x' && y == y' then r' else res) NONE table

-- --------------------------------------------------------------------- [ EOF ]


-- Import the Cairo prelude
import Cairo
import Data.List

-- Define a datatype
record Account where
  constructor MkAccount
  number: Felt
  balance: Felt

-- Read from private input (using inline Cairo)
%foreign
  """
  code:
  func Main_readFromInput(key) -> (result):
      tempvar result
      %{
          from starkware.cairo.common.math_utils import as_int
          val = program_input[ids.key]
          ids.result = as_int(val, PRIME) % PRIME
      %}
      return (result)
  end
  """
readFromInput : Felt -> Felt

-- Creates an account based on an index into the JSON array in `input.json`
createAccount : Felt -> Account
createAccount index = MkAccount number balance
  where number : Felt
        number = readFromInput index
        balance : Felt
        balance = readFromInput (index + 1)

-- List of available accounts (we assume there are 3 accounts)
privateAccounts : List Account
privateAccounts = map createAccount [0,2,4]

-- Define a function which gets a list of accounts and returns the sum of their balances
sumOfBalances : List Account -> Felt
sumOfBalances accounts = sum (map balance accounts)

-- Computes the hash of the account data (no need to pass the pedersen_ptr)
hashOfAccountData : List Account -> Felt
hashOfAccountData accounts = foldl pedersenHash 3 (foldMap (\a => [a.number, a.balance]) accounts)

-- The main entry point:
-- Computes and outputs the hash of the account data
-- together with the sum of the balances
main : Cairo ()
main = do
  output $ hashOfAccountData privateAccounts
  output $ sumOfBalances privateAccounts


module Flexidisc.Dec.IsYes

%default total

||| Build at type level the proof that a decidable property is valid
public export
data IsYes : prop -> Type where
  SoTrue : IsYes (Yes prop)

%name IsYes yes, prf, ok

||| Demote a proof from the type level to the value level
export
getProof : {witness : Dec prop} -> IsYes witness -> prop
getProof yes {witness = (Yes prf)} = prf
getProof yes {witness = (No contra)} impossible

||| If I can prove two times that a property doesn't hold,
||| the two proofs are equal
export
uniqueYes : (prop : Dec any) -> (x, y : IsYes prop) -> x = y
uniqueYes (Yes prf) SoTrue SoTrue = Refl
uniqueYes (No _) SoTrue _ impossible


module Ch03.Exercise_3_5_17

import Ch03.Arith

%default total

--------------------------------------------------------------------------------
-- Exercise 3.5.17
--------------------------------------------------------------------------------

||| Big step evaluation rules expressed in terms of reflexive-transitive closure
||| of small-step evaluation rules
data BInE : Term -> Term -> Type where
  BInEValue : {pf : IsValue v} -> BInE v v
  BInEIfTrue : {pf : IsValue v2} ->
               {t1, t2, t3 : Term} ->
               EvalsToStar t1 True ->
               EvalsToStar t2 v2 ->
               BInE (IfThenElse t1 t2 t3) v2
  BInEIfFalse : {pf : IsValue v3} ->
                {t1, t2, t3 : Term} ->
                EvalsToStar t1 False ->
                EvalsToStar t3 v3 ->
                BInE (IfThenElse t1 t2 t3) v3
  BInESucc : {pf : IsNumValue nv1} ->
             {t1 : Term} ->
             EvalsToStar t1 nv1 ->
             BInE (Succ t1) (Succ nv1)
  BInEPredZero : {t1 : Term} ->
                 EvalsToStar t1 Zero ->
                 BInE (Pred t1) Zero
  BInEPredSucc : {t1 : Term} ->
                 {pf : IsNumValue nv1} ->
                 EvalsToStar t1 (Succ nv1) ->
                 BInE (Pred t1) nv1
  BInEIsZeroZero : {t1 : Term} ->
                   EvalsToStar t1 Zero ->
                   BInE (IsZero t1) True
  BInEIsZeroSucc : {t1 : Term} ->
                   {pf : IsNumValue nv1} ->
                   EvalsToStar t1 (Succ nv1) ->
                   BInE (IsZero t1) False

||| Given a (one-step) derivation in the `BInE`-calculus, computes its corresponding derivation
||| in the `E`-calculus.
from_BInE_to_E : BInE t v -> EvalsToStar t v
from_BInE_to_E BInEValue = Refl
from_BInE_to_E (BInEIfTrue {t2} {t3} x y) = map {func=(\t => IfThenElse t t2 t3)} EIf x
                                            ++ Cons EIfTrue y
from_BInE_to_E (BInEIfFalse {t2} {t3} x y) = map {func=(\t => IfThenElse t t2 t3)} EIf x
                                            ++ Cons EIfFalse y
from_BInE_to_E (BInESucc x) = map ESucc x
from_BInE_to_E (BInEPredZero x) = map EPred x ++ weaken EPredZero
from_BInE_to_E (BInEPredSucc {pf} x) = map EPred x ++ weaken (EPredSucc {pf=pf})
from_BInE_to_E (BInEIsZeroZero x) = map EIsZero x ++ weaken EIsZeroZero
from_BInE_to_E (BInEIsZeroSucc {pf} x) = map EIsZero x ++ weaken (EIsZeroSucc {pf=pf})

--------------------------------------------------------------------------------
-- Sublemmas of `from_E_to_BInE`
--------------------------------------------------------------------------------

lemma_EIfTrue : {t2, t3 : Term} ->
                {pf : IsValue v} ->
                (d' : EvalsToStar t2 v) ->
                (r : BInE (IfThenElse True t2 t3) v ** Cons EIfTrue d' = from_BInE_to_E r)
lemma_EIfTrue {pf} d' = (BInEIfTrue {pf=pf} Refl d' ** Refl)

lemma_EIfFalse : {t2, t3 : Term} ->
                 {pf : IsValue v} ->
                 (d' : EvalsToStar t3 v) ->
                 (r : BInE (IfThenElse False t2 t3) v ** Cons EIfFalse d' = from_BInE_to_E r)
lemma_EIfFalse {pf} d' = (BInEIfFalse {pf=pf} Refl d' ** Refl)

lemma_EIf : {t1, t2, t3 : Term} ->
            {pf : IsValue v} ->
            {x : EvalsTo t1 t1'} ->
            (d' : EvalsToStar (IfThenElse t1' t2 t3) v) ->
            (r' : BInE (IfThenElse t1' t2 t3) v ** d' = from_BInE_to_E r') ->
            (r : BInE (IfThenElse t1 t2 t3) v ** Cons (EIf x) d' = from_BInE_to_E r)
lemma_EIf {pf} {x} d' (r' ** pf') = case r' of
                                         BInEValue {pf} => absurd (ifThenElseNotNormal pf)
                                         BInEIfTrue d1 d2 => (BInEIfTrue {pf=pf} (Cons x d1) d2 ** cong pf')
                                         BInEIfFalse d1 d2 => (BInEIfFalse {pf=pf} (Cons x d1) d2 ** cong pf')

lemma_ESucc : {t1 : Term} ->
              {pf : IsValue v} ->
              (x : EvalsTo t1 t1') ->
              (r' : BInE (Succ t1') v ** d' = from_BInE_to_E r') ->
              (r : BInE (Succ t1) v ** Cons (ESucc x) d' = from_BInE_to_E r)
lemma_ESucc {pf} x (r' ** pf') = case r' of
                                      BInEValue => case succIsValueIf pf of
                                                        nv_pf@(ConvertedFrom nv) => (BInESucc {pf=nv_pf} (weaken x) ** cong pf')
                                      BInESucc {pf} d'' => (BInESucc {pf=pf} (Cons x d'') ** cong pf')

lemma_EPred : {t1, t1' : Term} ->
              {pf : IsValue v} ->
              (x : EvalsTo t1 t1') ->
              (r' : BInE (Pred t1') v ** d' = from_BInE_to_E r') ->
              (r : BInE (Pred t1) v ** Cons (EPred x) d' = from_BInE_to_E r)
lemma_EPred {pf} x (r' ** pf') = case r' of
                                      BInEValue {pf=pf_val} => absurd (predNotValue pf_val)
                                      BInEPredZero y => (BInEPredZero (Cons x y) ** cong pf')
                                      BInEPredSucc {pf=pf_v} y => (BInEPredSucc {pf=pf_v} (Cons x y) ** cong pf')

lemma_EIsZero : {t1, t1', v : Term} ->
                {pf : IsValue v} ->
                (x : EvalsTo t1 t1') ->
                (r' : BInE (IsZero t1') v ** d' = from_BInE_to_E r') ->
                (r : BInE (IsZero t1) v ** Cons (EIsZero x) d' = from_BInE_to_E r)
lemma_EIsZero {pf} x (r' ** pf') = case r' of
                                        BInEValue {pf=pf_val} => absurd (isZeroNotValue pf_val)
                                        BInEIsZeroZero y => (BInEIsZeroZero (Cons x y) ** cong pf')
                                        BInEIsZeroSucc {pf=pf_v} y => (BInEIsZeroSucc {pf=pf_v} (Cons x y) ** cong pf')

||| Deconstructs a derivation of a term `t` to a value `v` in the `E`-calculus into a (one-step) derivation
||| in the `BInE`-calculus.
from_E_to_BInE : {pf : IsValue v} ->
                 (d : EvalsToStar t v) -> (r : BInE t v ** d = from_BInE_to_E r)
from_E_to_BInE {pf} {t = True} Refl = (BInEValue {pf=ConvertedFrom (Left True)} {v=True} ** Refl)
from_E_to_BInE {pf} {t = True} (Cons x y) = absurd (valuesDontEvaluate {pf=ConvertedFrom (Left True)} x)
from_E_to_BInE {pf} {t = False} Refl = (BInEValue {pf=ConvertedFrom (Left False)} {v=False} ** Refl)
from_E_to_BInE {pf} {t = False} (Cons x y) = absurd (valuesDontEvaluate {pf=ConvertedFrom (Left False)} x)
from_E_to_BInE {pf} {t = (IfThenElse x y z)} Refl = absurd (ifThenElseNotNormal pf)
from_E_to_BInE {pf} {t = (IfThenElse x y z)} (Cons w s) = case w of
                                                               EIfTrue => lemma_EIfTrue {pf=pf} s
                                                               EIfFalse => lemma_EIfFalse {pf=pf} s
                                                               (EIf r) => lemma_EIf {pf=pf} s (from_E_to_BInE {pf=pf} s)
from_E_to_BInE {pf} {t = Zero} d = case valuesAreNormal' {pf=ConvertedFrom (Right Zero)} d of
                                        Refl => case d of
                                                     Refl => (BInEValue {pf=ConvertedFrom (Right Zero)} ** Refl)
                                                     (Cons x y) => absurd (valuesDontEvaluate {pf=ConvertedFrom (Right Zero)} x)
from_E_to_BInE {pf} {t = (Succ x)} Refl = case succIsValueIf pf of
                                               ConvertedFrom nv => (BInEValue {pf=ConvertedFrom (Right (Succ nv))} ** Refl)
from_E_to_BInE {pf} {t = (Succ x)} (Cons y z) = case y of
                                                     ESucc y' => lemma_ESucc {pf=pf} y' (from_E_to_BInE {pf=pf} z)
from_E_to_BInE {pf} {t = (Pred x)} Refl = absurd (predNotValue pf)
from_E_to_BInE {pf} {t = (Pred x)} (Cons y z) = case y of
                                                     EPredZero => case valuesAreNormal {pf=ConvertedFrom (Right Zero)} z of
                                                                       Refl => (BInEPredZero Refl ** Refl)
                                                     EPredSucc {nv1} {pf=pf_nv} => case valuesAreNormal {pf=numValueIsValue pf_nv} z of
                                                                                Refl => (BInEPredSucc {pf=pf_nv} Refl ** Refl)
                                                     EPred y' => lemma_EPred {pf=pf} y' (from_E_to_BInE {pf=pf} z)
from_E_to_BInE {pf} {t = (IsZero x)} Refl = absurd (isZeroNotValue pf)
from_E_to_BInE {pf} {t = (IsZero x)} (Cons y z) = case y of
                                                       EIsZeroZero => case valuesAreNormal {pf=ConvertedFrom (Left True)} z of
                                                                           Refl => (BInEIsZeroZero Refl ** Refl)
                                                       EIsZeroSucc => case valuesAreNormal {pf=ConvertedFrom (Left False)} z of
                                                                           Refl => (BInEIsZeroSucc Refl ** Refl)
                                                       EIsZero y' => lemma_EIsZero {pf=pf} y' (from_E_to_BInE {pf=pf} z)

||| Converts a derivation in `BInE`-calculus to a derivation in the `B`-calculus.
from_BInE_to_B : {pf : IsValue v} -> BInE t v -> BigEvalsTo t v
from_BInE_to_B {pf} BInEValue = BValue {pf=pf}
from_BInE_to_B {pf} (BInEIfTrue y z) = let pf_true = ConvertedFrom (Left True)
                                           y' = fst (from_E_to_BInE {pf=pf_true} y)
                                           z' = fst (from_E_to_BInE {pf=pf} z) in
                                           BIfTrue {pf=pf} (from_BInE_to_B {pf=pf_true} y') (from_BInE_to_B {pf=pf} z')
from_BInE_to_B {pf} (BInEIfFalse y z) = let pf_false = ConvertedFrom (Left False)
                                            y' = fst (from_E_to_BInE {pf=pf_false} y)
                                            z' = fst (from_E_to_BInE {pf=pf} z) in
                                            BIfFalse {pf=pf} (from_BInE_to_B {pf=pf_false} y') (from_BInE_to_B {pf=pf} z')
from_BInE_to_B {pf} (BInESucc {pf=pf_nv} y) = let y' = fst (from_E_to_BInE {pf=numValueIsValue pf_nv} y) in
                                                  BSucc {pf=pf_nv} (from_BInE_to_B {pf=numValueIsValue pf_nv} y')
from_BInE_to_B {pf} (BInEPredZero x) = let pf_zero = ConvertedFrom (Right Zero)
                                           x' = fst (from_E_to_BInE {pf=pf_zero} x) in
                                           BPredZero (from_BInE_to_B {pf=pf_zero} x')
from_BInE_to_B {pf} (BInEPredSucc {pf=pf_v} y) = let pf_succ = numValueIsValue (succNumValueIsNumValue pf_v)
                                                     y' = fst (from_E_to_BInE {pf=pf_succ} y) in
                                                     BPredSucc {pf=pf_v} (from_BInE_to_B {pf=pf_succ} y')
from_BInE_to_B {pf} (BInEIsZeroZero x) = let pf_zero = ConvertedFrom (Right Zero)
                                             x' = fst (from_E_to_BInE {pf=pf_zero} x) in
                                             BIsZeroZero (from_BInE_to_B {pf=pf_zero} x')
from_BInE_to_B {pf} (BInEIsZeroSucc {pf=pf_v} y) = let pf_succ = numValueIsValue (succNumValueIsNumValue pf_v)
                                                       y' = fst (from_E_to_BInE {pf=pf_succ} y) in
                                                       BIsZeroSucc {pf=pf_v} (from_BInE_to_B {pf=pf_succ} y')

||| Proof that if a term `t` evaluates to a value `v` under the reflexive transitive
||| closure of the small-step evaluation rules, then it also evaluates to it under the
||| big-step evaluation rules.
starImpliesBig : {pf : IsValue v} -> EvalsToStar t v -> BigEvalsTo t v
starImpliesBig {pf} d = from_BInE_to_B {pf=pf} (fst (from_E_to_BInE {pf=pf} d))

||| Proof that if a term `t` evaluates to a value `v` under the big-step evaluation rules,
||| then it also evaluates to it under the reflexive transitive closure of the small-step
||| rules.
bigImpliesStar : {pf : IsValue v} -> BigEvalsTo t v -> EvalsToStar t v
bigImpliesStar {pf} BValue = Refl
bigImpliesStar {pf} (BIfTrue y z) = let y' = bigImpliesStar {pf=ConvertedFrom (Left True)} y
                                        z' = bigImpliesStar {pf=pf} z in
                                        from_BInE_to_E (BInEIfTrue {pf=pf} y' z')
bigImpliesStar {pf} (BIfFalse y z) = let y' = bigImpliesStar {pf=ConvertedFrom (Left False)} y
                                         z' = bigImpliesStar {pf=pf} z in
                                         from_BInE_to_E (BInEIfFalse {pf=pf} y' z')
bigImpliesStar {pf} (BSucc {pf=pf_nv} y) = let y' = bigImpliesStar {pf=numValueIsValue pf_nv} y in
                                               from_BInE_to_E (BInESucc {pf=pf_nv} y')
bigImpliesStar {pf} (BPredZero x) = let x' = bigImpliesStar {pf=pf} x in
                                        from_BInE_to_E (BInEPredZero x')
bigImpliesStar {pf} (BPredSucc {pf=pf_nv} y) = let y' = bigImpliesStar {pf=numValueIsValue (succNumValueIsNumValue pf_nv)} y in
                                                   from_BInE_to_E (BInEPredSucc {pf=pf_nv} y')
bigImpliesStar {pf} (BIsZeroZero x) = let x' = bigImpliesStar {pf=ConvertedFrom (Right Zero)} x in
                                          from_BInE_to_E (BInEIsZeroZero x')
bigImpliesStar {pf} (BIsZeroSucc {pf=pf_nv} y) = let y' = bigImpliesStar {pf=numValueIsValue (succNumValueIsNumValue pf_nv)} y in
                                                     from_BInE_to_E (BInEIsZeroSucc {pf=pf_nv} y')