/-
Copyright (c) 2016 Microsoft Corporation. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Author: Leonardo de Moura
-/
prelude
import init.core init.data.nat.basic
open Decidable List

universes u v w

instance (α : Type u) : Inhabited (List α) :=
⟨List.nil⟩

variables {α : Type u} {β : Type v} {γ : Type w}

namespace List

protected def hasDecEq [DecidableEq α] : Π a b : List α, Decidable (a = b)
| []      []      := isTrue rfl
| (a::as) []      := isFalse (λ h, List.noConfusion h)
| []      (b::bs) := isFalse (λ h, List.noConfusion h)
| (a::as) (b::bs) :=
  match decEq a b with
  | isTrue hab  :=
    match hasDecEq as bs with
    | isTrue habs  := isTrue (Eq.subst hab (Eq.subst habs rfl))
    | isFalse nabs := isFalse (λ h, List.noConfusion h (λ _ habs, absurd habs nabs))
  | isFalse nab := isFalse (λ h, List.noConfusion h (λ hab _, absurd hab nab))

instance [DecidableEq α] : DecidableEq (List α) :=
{decEq := List.hasDecEq}

def reverseAux : List α → List α → List α
| []     r := r
| (a::l) r := reverseAux l (a::r)

def reverse : List α → List α :=
λ l, reverseAux l []

protected def append (as bs : List α) : List α :=
reverseAux as.reverse bs

instance : HasAppend (List α) :=
⟨List.append⟩

theorem reverseAuxReverseAuxNil : ∀ (as bs : List α), reverseAux (reverseAux as bs) [] = reverseAux bs as
| []  bs     := rfl
| (a::as) bs :=
  show reverseAux (reverseAux as (a::bs)) [] = reverseAux bs (a::as), from
  reverseAuxReverseAuxNil as (a::bs)

theorem nilAppend (as : List α) : [] ++ as = as :=
rfl

theorem appendNil (as : List α) : as ++ [] = as :=
show reverseAux (reverseAux as []) [] = as, from
reverseAuxReverseAuxNil as []

theorem reverseAuxReverseAux : ∀ (as bs cs : List α), reverseAux (reverseAux as bs) cs = reverseAux bs (reverseAux (reverseAux as []) cs)
| []      bs cs := rfl
| (a::as) bs cs :=
  Eq.trans
    (reverseAuxReverseAux as (a::bs) cs)
    (congrArg (λ b, reverseAux bs b) (reverseAuxReverseAux as [a] cs).symm)

theorem consAppend (a : α) (as bs : List α) : (a::as) ++ bs = a::(as ++ bs) :=
reverseAuxReverseAux as [a] bs

theorem appendAssoc : ∀ (as bs cs : List α), (as ++ bs) ++ cs = as ++ (bs ++ cs)
| []      bs cs := rfl
| (a::as) bs cs :=
  show ((a::as) ++ bs) ++ cs = (a::as) ++ (bs ++ cs),      from
  have h₁ : ((a::as) ++ bs) ++ cs = a::(as++bs) ++ cs,     from congrArg (++ cs) (consAppend a as bs),
  have h₂ : a::(as++bs) ++ cs     = a::((as++bs) ++ cs),   from consAppend a (as++bs) cs,
  have h₃ : a::((as++bs) ++ cs)   = a::(as ++ (bs ++ cs)), from congrArg (λ as, a::as) (appendAssoc as bs cs),
  have h₄ : a::(as ++ (bs ++ cs)) = (a::as ++ (bs ++ cs)), from (consAppend a as (bs++cs)).symm,
  Eq.trans (Eq.trans (Eq.trans h₁ h₂) h₃) h₄

inductive Mem : α → List α → Prop
| eqHead (a : α) (as : List α) : Mem a (a::as)
| inTail {a : α} (b : α) {bs : List α} (h : Mem a bs) : Mem a (b::bs)

instance : HasMem α (List α) :=
⟨Mem⟩

theorem notMem : ∀ {a b : α} {bs : List α}, a ≠ b → ¬ a ∈ bs → ¬ a ∈ b :: bs
| _ _ _ h _  (Mem.eqHead _ _)  := absurd rfl h
| _ _ _ _ h₁ (Mem.inTail _ h₂) := absurd h₂ h₁

instance decidableMem [DecidableEq α] (a : α) : ∀ (l : List α), Decidable (a ∈ l)
| []      := isFalse (λ h, match h with end)
| (b::bs) :=
  if h₁ : a = b then isTrue (h₁.symm ▸ Mem.eqHead b bs)
  else match decidableMem bs with
       | isTrue  h₂ := isTrue (Mem.inTail _ h₂)
       | isFalse h₂ := isFalse (notMem h₁ h₂)

instance : HasEmptyc (List α) :=
⟨List.nil⟩

protected def erase {α} [DecidableEq α] : List α → α → List α
| []     b := []
| (a::l) b := if a = b then l else a :: erase l b

def lengthAux : List α → Nat → Nat
| []      n := n
| (a::as) n := lengthAux as (n+1)

def length (as : List α) : Nat :=
lengthAux as 0

def isEmpty : List α → Bool
| []       := true
| (_ :: _) := false

def get [Inhabited α] : Nat → List α → α
| 0     (a::as) := a
| (n+1) (a::as) := get n as
| _     _       := default α

def getOpt : Nat → List α → Option α
| 0     (a::as) := some a
| (n+1) (a::as) := getOpt n as
| _     _       := none

def set : List α → Nat → α → List α
| (a::as) 0     b := b::as
| (a::as) (n+1) b := a::(set as n b)
| []      _     _ := []

def head [Inhabited α] : List α → α
| []     := default α
| (a::_) := a

def tail : List α → List α
| []      := []
| (a::as) := as

@[specialize] def map (f : α → β) : List α → List β
| []      := []
| (a::as) := f a :: map as

@[specialize] def map₂ (f : α → β → γ) : List α → List β → List γ
| []      _       := []
| _       []      := []
| (a::as) (b::bs) := f a b :: map₂ as bs

def join : List (List α) → List α
| []        := []
| (a :: as) := a ++ join as

@[specialize] def filterMap (f : α → Option β) : List α → List β
| []     := []
| (a::as) :=
  match f a with
  | none   := filterMap as
  | some b := b :: filterMap as

@[specialize] def filterAux (p : α → Bool) : List α → List α → List α
| []      rs := rs.reverse
| (a::as) rs := match p a with
   | true  := filterAux as (a::rs)
   | false := filterAux as rs

@[inline] def filter (p : α → Bool) (as : List α) : List α :=
filterAux p as []

@[specialize] def partitionAux (p : α → Bool) : List α → List α × List α → List α × List α
| []      (bs, cs) := (bs.reverse, cs.reverse)
| (a::as) (bs, cs) :=
  match p a with
  | true  := partitionAux as (a::bs, cs)
  | false := partitionAux as (bs, a::cs)

@[inline] def partition (p : α → Bool) (as : List α) : List α × List α :=
partitionAux p as ([], [])

def dropWhile (p : α → Bool) : List α → List α
| []     := []
| (a::l) := match p a with
            | true  := dropWhile l
            | false :=  a::l

def find (p : α → Bool) : List α → Option α
| []      := none
| (a::as) := match p a with
  | true  := some a
  | false := find as

def elem [HasBeq α] (a : α) : List α → Bool
| []      := false
| (b::bs) := match a == b with
  | true  := true
  | false := elem bs

def notElem [HasBeq α] (a : α) (as : List α) : Bool :=
!(as.elem a)

@[specialize] def spanAux (p : α → Bool) : List α → List α → List α × List α
| []      rs := (rs.reverse, [])
| (a::as) rs := match p a with
  | true  := spanAux as (a::rs)
  | false := (rs.reverse, a::as)

@[inline] def span (p : α → Bool) (as : List α) : List α × List α :=
spanAux p as []

def lookup [HasBeq α] : α → List (α × β) → Option β
| _ []          := none
| a ((k,b)::es) := match a == k with
  | true  := some b
  | false := lookup a es

def removeAll [HasBeq α] (xs ys : List α) : List α :=
xs.filter (λ x, ys.notElem x)

def drop : Nat → List α → List α
| 0     a       := a
| (n+1) []      := []
| (n+1) (a::as) := drop n as

def take : Nat → List α → List α
| 0     a       := []
| (n+1) []      := []
| (n+1) (a::as) := a :: take n as

@[specialize] def foldl (f : α → β → α) : α → List β → α
| a []       := a
| a (b :: l) := foldl (f a b) l

@[specialize] def foldr (f : α → β → β) (b : β) : List α → β
| []       := b
| (a :: l) := f a (foldr l)

@[specialize] def foldr1 (f : α → α → α) : Π (xs : List α), xs ≠ [] → α
| []               h := absurd rfl h
| [a]              _ := a
| (a :: as@(_::_)) _ := f a (foldr1 as (λ h, List.noConfusion h))

@[specialize] def foldr1Opt (f : α → α → α) : List α → Option α
| []        := none
| (a :: as) := some $ foldr1 f (a :: as) (λ h, List.noConfusion h)

@[inline] def any (l : List α) (p : α → Bool) : Bool :=
foldr (λ a r, p a || r) false l

@[inline] def all (l : List α) (p : α → Bool) : Bool :=
foldr (λ a r, p a && r) true l

def or  (bs : List Bool) : Bool := bs.any id

def and (bs : List Bool) : Bool := bs.all id

def zipWith (f : α → β → γ) : List α → List β → List γ
| (x::xs) (y::ys) := f x y :: zipWith xs ys
| _       _       := []

def zip : List α → List β → List (Prod α β) :=
zipWith Prod.mk

def unzip : List (α × β) → List α × List β
| []            := ([], [])
| ((a, b) :: t) := match unzip t with | (al, bl) := (a::al, b::bl)

protected def insert [DecidableEq α] (a : α) (l : List α) : List α :=
if a ∈ l then l else a :: l

instance [DecidableEq α] : HasInsert α (List α) :=
⟨List.insert⟩

def replicate (n : Nat) (a : α) : List α :=
n.repeat (λ xs, a :: xs) []

def rangeAux : Nat → List Nat → List Nat
| 0     ns := ns
| (n+1) ns := rangeAux n (n::ns)

def range (n : Nat) : List Nat :=
rangeAux n []

def iota : Nat → List Nat
| 0       := []
| m@(n+1) := m :: iota n

def enumFrom : Nat → List α → List (Nat × α)
| n [] := nil
| n (x :: xs) := (n, x) :: enumFrom (n + 1) xs

def enum : List α → List (Nat × α) := enumFrom 0

def getLastOfNonNil : Π (as : List α), as ≠ [] → α
| []         h := absurd rfl h
| [a]        h := a
| (a::b::as) h := getLastOfNonNil (b::as) (λ h, List.noConfusion h)

def getLast [Inhabited α] : List α → α
| []      := arbitrary α
| (a::as) := getLastOfNonNil (a::as) (λ h, List.noConfusion h)

def init : List α → List α
| []     := []
| [a]    := []
| (a::l) := a::init l

def intersperse (sep : α) : List α → List α
| []      := []
| [x]     := [x]
| (x::xs) := x::sep::intersperse xs

def intercalate (sep : List α) (xs : List (List α)) : List α :=
join (intersperse sep xs)

@[inline] protected def bind {α : Type u} {β : Type v} (a : List α) (b : α → List β) : List β :=
join (map b a)

@[inline] protected def pure {α : Type u} (a : α) : List α :=
[a]

inductive Less [HasLess α] : List α → List α → Prop
| nil  (b : α) (bs : List α) : Less [] (b::bs)
| head {a : α} (as : List α) {b : α} (bs : List α) : a < b → Less (a::as) (b::bs)
| tail {a : α} {as : List α} {b : α} {bs : List α} : ¬ a < b → ¬ b < a → Less as bs → Less (a::as) (b::bs)

instance [HasLess α] : HasLess (List α) :=
⟨List.Less⟩

instance hasDecidableLt [HasLess α] [h : DecidableRel ((<) : α → α → Prop)] : Π l₁ l₂ : List α, Decidable (l₁ < l₂)
| []      []      := isFalse (λ h, match h with end)
| []      (b::bs) := isTrue (Less.nil _ _)
| (a::as) []      := isFalse (λ h, match h with end)
| (a::as) (b::bs) :=
  match h a b with
  | isTrue h₁  := isTrue (Less.head _ _ h₁)
  | isFalse h₁ :=
    match h b a with
    | isTrue h₂  := isFalse (λ h, match h with
       | Less.head _ _ h₁' := absurd h₁' h₁
       | Less.tail _ h₂' _ := absurd h₂ h₂')
    | isFalse h₂ :=
      match hasDecidableLt as bs with
      | isTrue h₃  := isTrue (Less.tail h₁ h₂ h₃)
      | isFalse h₃ := isFalse (λ h, match h with
         | Less.head _ _ h₁' := absurd h₁' h₁
         | Less.tail _ _ h₃' := absurd h₃' h₃)

@[reducible] protected def LessEq [HasLess α] (a b : List α) : Prop :=
¬ b < a

instance [HasLess α] : HasLessEq (List α) :=
⟨List.LessEq⟩

instance hasDecidableLe [HasLess α] [h : DecidableRel ((<) : α → α → Prop)] : Π l₁ l₂ : List α, Decidable (l₁ ≤ l₂) :=
λ a b, Not.Decidable

/--  `isPrefixOf l₁ l₂` returns `true` Iff `l₁` is a prefix of `l₂`. -/
def isPrefixOf [HasBeq α] : List α → List α → Bool
| []      _       := true
| _       []      := false
| (a::as) (b::bs) := a == b && isPrefixOf as bs

/--  `isSuffixOf l₁ l₂` returns `true` Iff `l₁` is a suffix of `l₂`. -/
def isSuffixOf [HasBeq α] (l₁ l₂ : List α) : Bool :=
isPrefixOf l₁.reverse l₂.reverse

@[specialize] def isEqv : List α → List α → (α → α → Bool) → Bool
| []      []      _   := true
| (a::as) (b::bs) eqv := eqv a b && isEqv as bs eqv
| _       _       eqv := false
end List
