import Data.Char
import Test.QuickCheck

--------------------------------------------------------------------

{-
We have seen the datatype for Hand (cardplay):

data Hand = Empty | Add Card Hand

This seems to be a nice solution, that could be of general interest.
In general, when modelling a "collection" of things, we could use
the same principle.

Let us try to define a datatype for lists:

data List = Empty | Add ? List

So, a list is either:

  * Empty

  * or it has at least one element (the first), and we model the rest
    of the elements with a list again

What should be on the place of the ? question mark? Answer:
this should be decided by the persion who uses this type, so
we turn this into a parameter.
-}

--------------------------------------------------------------------

-- a datatype for lists
data List a = Empty | Add a (List a)
  deriving ( Show, Eq )

{-
-- we can now model a Hand by using a List
type Hand = List Card
-}

-- tom list checks whether list is empty
tom :: List a -> Bool
tom Empty        = True
tom (Add x list) = False

{-
The type of 'tom', namely

  tom :: List a -> Bool

means that it works for ANY type a. Note the difference between type
names starting with a capital:

  Integer, Bool, Float, List, etc.

which denote specific types, and type names with a small letter:

  a, b, c, etc.

which denote any type.
-}

-- forst list returns the first element of the list
forst :: List a -> a
forst (Add x list) = x

{-
The type of 'forst', namely

  forst :: List a -> a

means that it works for ANY type a. Note that the element type
and the result type should match; they should both be 'a'.
-}

{-
-- alternatively, we can generate an error when the list is empty:
forst :: List a -> a
forst Empty        = error "the list is empty"
forst (Add x list) = x
-}

-- Note: What is the type of 'error'?

-- sist list returns the last element of the list
sist :: List a -> a
sist Empty         = error "the list is empty"
sist (Add x Empty) = x
sist (Add x list)  = sist list -- here, we know that list is non-empty

--------------------------------------------------------------------

{-
There is standard notation in Haskell for the above datatype and
functions.

  []            is used for the empty list ('Empty' above)
  [1,2,3,4,2]   is a list with 5 elements
  [3]           is a list with one element
  x : list      is a list with first element x, and list as the rest
                ('Add x list' above)
  [Integer]     is the type of lists of Integers ('List Integer' above)
-}

{-
-- using the Haskell built-in standard lists and notation, the above
-- examples become:

-- tom xs checks whether list xs is empty
tom :: [a] -> Bool
tom []     = True
tom (x:xs) = False

-- forst xs returns the first element of the list xs
forst :: List a -> a
forst []     = error "the list is empty"
forst (x:xs) = x

-- sist xs returns the last element of the list xs
sist :: List a -> a
sist []     = error "the list is empty"
sist [x]    = x
sist (x:xs) = sist xs
-}

{-
-- alternatively:
sist :: List a -> a
sist []     = error "the list is empty"
sist (x:[]) = x
sist (x:xs) = sist xs
-}

--------------------------------------------------------------------

-- size l (a.k.a length) returns the number of elements in l
size :: [a] -> Int
size []     = 0
size (x:xs) = 1 + size xs

-- summan xs (a.k.a sum) calculates the sum of all elements in xs
summan :: Num a => [a] -> a
summan []     = 0
summan (x:xs) = x + summan xs

{-
The type of 'summan', namely

  summan :: Num a => [a] -> a

means that it works for ANY type a, as long as type is a "numeric" or
"Num" type. This means that it works for any type for which we have
the normal arithmetic operations +, -, *, etc.

Note: type ":i Num" in GHCi and see.
-}

-- mest l (a.k.a. maximum) returns the largest element in l
mest :: Ord a => [a] -> a
mest [x]    = x
mest (x:xs) = max x (mest xs)

--------------------------------------------------------------------

-- l1 +++ l2 (a.k.a. ++) appends l2 after l1
(+++) :: [a] -> [a] -> [a]
[]     +++ ys = ys
(x:xs) +++ ys = x : (xs +++ ys)

-- rev (a.k.a. reverse) reverses the elements in l
rev :: [a] -> [a]
rev []     = []
rev (x:xs) = rev xs +++ [x]

-- value s returns the number represented by the string s as an integer
value :: String -> Integer
value s = calculate (reverse s)

-- Helper function for value
calculate :: String -> Integer
calculate ""    = 0
calculate (c:s) = toInteger (digitToInt c) + 10 * calculate s

prop_Value n =
  n >= 0 ==>
    value (show n) == n

--------------------------------------------------------------------