--
--
-- Collection
--
--

--
-- List
--
list a :=
  matcher
    | [] as () with
      | [] -> [()]
      | _ -> []
    | $ :: $ as (a, list a) with
      | $x :: $xs -> [(x, xs)]
      | _ -> []
    | snoc $ $ as (a, list a) with
      | snoc $xs $x -> [(x, xs)]
      | _ -> []
    | _ ++ $ as (list a) with
      | $tgt ->
        matchAll tgt as list a with
          | loop $i (1, _)
              (_ :: ...)
              $rs -> rs
    | $ ++ $ as (list a, list a) with
      | $tgt ->
        matchAll tgt as list a with
          | loop $i (1, $n)
              ($xa_i :: ...)
              $rs -> (foldr (\%i %r -> xa_i :: r) [] [1..n], rs)
    | nioj $ $ as (list a, list a) with
      | $tgt ->
        matchAll tgt as list a with
          | loop $i (1, $n)
              (snoc $xa_i ...)
              $rs -> (foldr (\%i %r -> r ++ [xa_i]) [] [1..n], rs)
    | #$val as () with
      | $tgt -> if val = tgt then [()] else []
    | $ as (something) with
      | $tgt -> [tgt]

sortedList a :=
  matcher
    | [] as () with
      | [] -> [()]
      | _ -> []
    | $ ++ #$px :: $ as (sortedList a, sortedList a) with
      | $tgt ->
        matchAll tgt as list a with
          | loop $i (1, $n)
              ((?(< px) & $xa_i) :: ...)
              (#px :: $rs) -> (map (\i -> xa_i) [1..n], rs)
    | $ ++ $ as (sortedList a, sortedList a) with
      | $tgt ->
        matchAll tgt as list a with
          | loop $i (1, $n)
              ($xa_i :: ...)
              $rs -> (map (\i -> xa_i) [1..n], rs)
    | $ :: $ as (a, sortedList a) with
      | $x :: $xs -> [(x, xs)]
      | _ -> []
    | #$val as () with
      | $tgt -> if val = tgt then [()] else []
    | $ as (something) with
      | $tgt -> [tgt]

--
-- Accessors
--
nth n xs :=
  match xs as list something with
    | loop $i (1, n - 1, _)
        (_ :: ...)
        ($x :: _) -> x

takeAndDrop n xs :=
  match xs as list something with
    | loop $i (1, n, _)
        ($a_i :: ...)
        $rs -> (map (\i -> a_i) [1..n], rs)

take n xs :=
  if n = 0
    then []
    else match xs as list something with
      | $x :: $xs -> x :: take (n - 1) xs
      | [] -> []

drop n xs :=
  if n = 0
    then xs
    else match xs as list something with
      | _ :: $xs -> drop (n - 1) xs
      | [] -> []

takeWhile pred xs :=
  match xs as list something with
    | [] -> []
    | $x :: $rs -> if pred x then x :: takeWhile pred rs else []

takeWhileBy pred xs :=
  match xs as list something with
    | [] -> []
    | $x :: $rs -> if pred x then x :: takeWhileBy pred rs else [x]

taileUntil pred xs :=
  match xs as list something with
    | [] -> []
    | $x :: $rs -> if not (pred x) then x :: takeUntil pred rs else []

takeUntilBy pred xs :=
  match xs as list something with
    | [] -> []
    | $x :: $rs -> if not (pred x) then x :: takeUntilBy pred rs else [x]

dropWhile pred xs :=
  match xs as list something with
    | [] -> []
    | $x :: $rs -> if pred x then dropWhile pred rs else xs

--
-- head, tail, uncons, unsnoc
--
head xs :=
  match xs as list something with
    | $x :: _ -> x

tail xs :=
  match xs as list something with
    | _ :: $ys -> ys

last xs :=
  match xs as list something with
    | snoc $x _ -> x

init xs :=
  match xs as list something with
    | snoc _ $ys -> ys

uncons xs :=
  match xs as list something with
    | $x :: $ys -> (x, ys)

unsnoc xs :=
  match xs as list something with
    | snoc $x $ys -> (ys, x)


--
-- list functions
--
isEmpty xs :=
  match xs as list something with
    | [] -> True
    | _  -> False

length xs := foldl 2#(%1 + 1) 0 xs

map fn xs :=
  match xs as list something with
    | [] -> []
    | $x :: $rs -> fn x :: map fn rs

map2 fn xs ys :=
  match (xs, ys) as (list something, list something) with
    | ([], _) -> []
    | (_, []) -> []
    | ($x :: $xs2, $y :: $ys2) -> fn x y :: map2 fn xs2 ys2

map3 fn xs ys zs :=
  match (xs, ys, zs) as (list something, list something, list something) with
    | ([], _, _) -> []
    | (_, [], _) -> []
    | (_, _, []) -> []
    | ($x :: $xs2, $y :: $ys2, $z :: $zs2) -> fn x y z :: map3 fn xs2 ys2 zs2

map4 fn xs ys zs ws :=
  match (xs, ys, zs, ws) as
    (list something, list something, list something, list something) with
    | ([], _, _, _) -> []
    | (_, [], _, _) -> []
    | (_, _, [], _) -> []
    | (_, _, _, []) -> []
    | ($x :: $xs2, $y :: $ys2, $z :: $zs2, $w :: $ws2) ->
      fn x y z w :: map4 fn xs2 ys2 zs2 ws2

filter pred xs := foldr (\%y %ys -> if pred y then y :: ys else ys) [] xs

partition pred xs := (filter pred xs, filter 1#(not (pred %1)) xs)

zip xs ys := map2 (\x y -> (x, y)) xs ys

zip3 xs ys zs := map3 (\x y z -> (x, y, z)) xs ys zs

zip4 xs ys zs ws := map4 (\x y z w -> (x, y, z, w)) xs ys zs ws

lookup k ls :=
  match ls as list (something, something) with
    | _ ++ (#k, $x) :: _ -> x

foldr fn %init %ls :=
  match ls as list something with
    | [] -> init
    | $x :: $xs -> fn x (foldr fn init xs)

foldl fn %init %ls :=
  match ls as list something with
    | [] -> init
    | $x :: $xs ->
      let z := fn init x
       in seq z (foldl fn z xs)

foldl1 fn %ls := foldl fn (head ls) (tail ls)

reduce fn %ls := foldl fn (head ls) (tail ls)

scanl fn %init %ls :=
  init :: (match ls as list something with
    | [] -> []
    | $x :: $xs -> scanl fn (fn init x) xs)

iterate fn %x :=
  let nx1 := fn x
      nx2 := fn nx1
      nx3 := fn nx2
      nx4 := fn nx3
      nx5 := fn nx4
   in x :: nx1 :: nx2 :: nx3 :: nx4 :: iterate fn nx5

repeatedSquaring fn %x n :=
  match n as integer with
    | #1 -> x
    | ?isEven ->
      let y := repeatedSquaring fn x (quotient n 2)
       in fn y y
    | ?isOdd ->
      let y := repeatedSquaring fn x (quotient n 2)
       in fn (fn y y) x

append xs ys := xs ++ ys

concat xss := foldr (\%xs %rs -> xs ++ rs) [] xss

reverse xs :=
  match xs as list something with
    | [] -> []
    | snoc $x $rs -> x :: reverse rs

intersperse sep ws :=
  match ws as list something with
    | [] -> []
    | $w :: $rs -> foldl (\s1 s2 -> s1 ++ [sep, s2]) [w] rs

intercalate := compose intersperse concat

split sep ls :=
  match ls as list something with
    | $xs ++ #sep ++ $rs -> xs :: split sep rs
    | _ -> [ls]

splitAs a sep ls :=
  match ls as list a with
    | $xs ++ #sep ++ $rs -> xs :: splitAs a sep rs
    | _ -> [ls]

findCycle xs :=
  head
    (matchAll xs as list something with
      | $ys ++ (_ :: _ & $cs) ++ #cs ++ _ -> (ys, cs))

repeat %xs := xs ++ repeat xs

repeat1 %x := x :: repeat1 x

--
-- Others
--
all pred xs :=
  match xs as list something with
    | [] -> True
    | $x :: $rs -> if pred x then all pred rs else False

any pred xs :=
  match xs as list something with
    | [] -> False
    | $x :: $rs -> if pred x then True else any pred rs

from s :=
  [s, s + 1, s + 2, s + 3, s + 4, s + 5, s + 6, s + 7, s + 8, s + 9, s + 10] ++
    from (s + 11)

-- Note. `between` is used in the definition of the list matcher.
between s e :=
  if s = e then [s] else if s < e then s :: between (s + 1) e else []

L./ xs ys :=
  if length xs < length ys
    then ([], xs)
    else match (ys, xs) as (list mathExpr, list mathExpr) with
      | ($y :: $yrs, $x :: $xrs) ->
        let (zs, rs) := L./
                          (map2
                             (-)
                             (take (length yrs) xrs)
                             (map 1#(%1 * (x / y)) yrs) ++ drop (length yrs) xrs)
                          ys
         in (x / y :: zs, rs)

--
-- Multiset
--
multiset a :=
  matcher
    | [] as () with
      | [] -> [()]
      | _ -> []
    | $ :: _ as (a) with
      | $tgt -> tgt
    | $ :: $ as (a, multiset a) with
      | $tgt ->
        matchAll tgt as list a with
          | $hs ++ $x :: $ts -> (x, hs ++ ts)
    | #$pxs ++ $ as (multiset a) with
      | $tgt ->
        match (pxs, tgt) as (list a, multiset a) with
          | loop $i (1, length pxs, _)
              {($x_i :: @, #x_i :: @), ...}
              ([], $rs) -> [rs]
          | _ -> []
    | $ ++ $ as (multiset a, multiset a) with
      | $tgt ->
        matchAll tgt as list a with
          | loop $i (1, $n)
              ($rs_i ++ $x_i :: ...)
              $ts ->
            (map (\i -> x_i) [1..n], concat (map (\i -> rs_i) [1..n] ++ [ts]))
    | #$val as () with
      | $tgt ->
        match (val, tgt) as (list a, multiset a) with
          | ([], []) -> [()]
          | ($x :: $xs, #x :: #xs) -> [()]
          | (_, _) -> []
    | $ as (something) with
      | $tgt -> [tgt]

--
-- multiset operation
--
deleteFirst %x xs :=
  match xs as list something with
    | [] -> []
    | #x :: $rs -> rs
    | $y :: $rs -> y :: deleteFirst x rs

deleteFirstAs a %x xs :=
  match xs as list a with
    | [] -> []
    | #x :: $rs -> rs
    | $y :: $rs -> y :: deleteFirstAs a x rs

delete x xs :=
  match xs as list something with
    | [] -> []
    | $hs ++ #x :: $ts -> hs ++ delete x ts
    | _ -> xs

deleteAs a x xs :=
  match xs as list a with
    | [] -> []
    | $hs ++ #x :: $ts -> hs ++ deleteAs a x ts
    | _ -> xs

difference xs ys :=
  match ys as list something with
    | [] -> xs
    | $y :: $rs -> difference (deleteFirst y xs) rs

differenceAs a xs ys :=
  match ys as list a with
    | [] -> xs
    | $y :: $rs -> differenceAs a (deleteFirstAs a y xs) rs

include xs ys :=
  match ys as list something with
    | [] -> True
    | $y :: $rs ->
      if member y xs then include (deleteFirst y xs) rs else False

includeAs a xs ys :=
  match ys as list a with
    | [] -> True
    | $y :: $rs ->
      if memberAs a y xs then includeAs a (deleteFirst y xs) rs else False

union xs ys :=
  xs ++ (matchAll (ys, xs) as (multiset something, multiset something) with
    | ($y :: _, !(#y :: _)) -> y)

unionAs a xs ys :=
  xs ++ (matchAll (ys, xs) as (multiset a, multiset a) with
    | ($y :: _, !(#y :: _)) -> y)

intersect xs ys :=
  matchAll (xs, ys) as (multiset something, multiset something) with
    | ($x :: _, #x :: _) -> x

intersectAs a xs ys :=
  matchAll (xs, ys) as (multiset a, multiset a) with
    | ($x :: _, #x :: _) -> x

--
-- Simple predicate
--
member x ys :=
  match ys as list something with
    | _ ++ #x :: _ -> True
    | _ -> False

memberAs a x ys :=
  match ys as list a with
    | _ ++ #x :: _ -> True
    | _ -> False

--
-- Counting
--
count x xs :=
  foldl
    (\match as (something, something) with
      | ($r, #x) -> r + 1
      | ($r, $y) -> r)
    0
    xs

countAs a x xs :=
  foldl
    (\match as (a, a) with
      | ($r, #x) -> r + 1
      | ($r, $y) -> r)
    0
    xs

frequency xs :=
  let us := unique xs
   in map (\u -> (u, count u xs)) us

frequencyAs a xs :=
  let us := uniqueAs a xs
   in map (\u -> (u, countAs a u xs)) us

--
-- Index
--
elemIndices x xs :=
  matchAll xs as list something with
    | $hs ++ #x :: _ -> 1 + length hs

--
-- Set
--
set a :=
  matcher
    | [] as () with
      | [] -> [()]
      | _ -> []
    | $ :: $ as (a, set a) with
      | $tgt ->
        matchAll tgt as list a with
          | _ ++ $x :: _ -> (x, tgt)
    | #$pxs ++ $ as (set a) with
      | $tgt ->
        match (pxs, tgt) as (list a, set a) with
          | ( loop $i (1, $n) ($x_i :: ...) []
            , loop $i (1, n)  (#x_i :: ...) _ ) -> [tgt]
          | _ -> []
    | $ ++ $ as (set a, set a) with
      | $tgt ->
        matchAll tgt as list a with
          | loop $i (1, $n)
              ($rs_i ++ $x_i :: ...)
              $ts -> (map (\i -> x_i) [1..n], tgt)
    | #$val as () with
      | $tgt ->
        match (unique val, unique tgt) as (list a, multiset a) with
          | ([], []) -> [()]
          | ($x :: $xs, #x :: #xs) -> [()]
          | (_, _) -> []
    | $ as (something) with
      | $tgt -> [tgt]

--
-- set operation
--
add x xs := if member x xs then xs else xs ++ [x]

addAs a x xs := if memberAs a x xs then xs else xs ++ [x]

fastUnique xs :=
  matchAll sort xs as list something with
    | _ ++ $x :: !(#x :: _) -> x

unique xs :=
  reverse
    (matchAll reverse xs as list something with
      | _ ++ $x :: !(_ ++ #x :: _) -> x)

uniqueAs a xs := loopFn xs []
  where
    loopFn xs ys :=
      match (xs, ys) as (list a, multiset a) with
        | ([], _) -> ys
        | ($x :: $rs, #x :: _) -> loopFn rs ys
        | ($x :: $rs, _) -> loopFn rs (ys ++ [x])
