--
-- the star attraction!
--

self_ref :: Char -> [Char]
-- 't' -> "'t' is the first, fourth, eleventh, sixteenth, twenty-fourth,
--           twenty-ninth, ..." (!), a la Hofstadter's Metamagical Themas.

self_ref ch = "'" ++ [ch] ++ "' is the "
                  ++ (concat
                      . map ((++", ").show_position_in_English)
                      . find_positions ch
                      . filter is_alphanumeric
                      . self_ref
                     ) ch



--
-- support functions.
--

find_positions :: Eq a => a -> [a] -> [Integer]
-- find all positions of element in list, numbering from 1.

find_positions = find_positions_from 1
                 where
                 find_positions_from n x [] = []
                 find_positions_from n x (h:t)
                   | h == x = n : find_positions_from (n+1) x t
                   | h /= x =     find_positions_from (n+1) x t



show_position_in_English :: Integer -> [Char]
-- 251 -> "two hundred and fifty-first", etc.

show_position_in_English
  = init . concat . map add_padding . show_position_in_structured_English
    where
    add_padding l | last l == '-' = l
                  | last l /= '-' = l ++ " "



show_position_in_structured_English :: Integer -> [[Char]]
-- 251 -> ["two", "hundred", "and", "fifty-", "first"], etc.

show_position_in_structured_English n
  = init nstruct ++ [search_and_replace (last nstruct)]
    where
    nstruct = show_in_structured_English n
    search_and_replace l
      | l == "one"    = "first"
      | l == "two"    = "second"
      | l == "three"  = "third"
      | l == "five"   = "fifth"
      | l == "eight"  = "eighth"
      | l == "nine"   = "ninth"
      | l == "twelve" = "twelfth"
      | last l == 'y' = init l ++ "ieth"
      | otherwise     = l ++ "th"



show_in_structured_English :: Integer -> [[Char]]
-- 251 -> ["two", "hundred", "and", "fifty-", "one"], etc.

show_in_structured_English n
  | n<1000000 = upto999999 n
  | n>=1000000 && n `mod` 1000000 == 0
              = [show (n `div` 1000000), "million"]
  | n>=1000000 && n `mod` 1000000 > 0 && n `mod` 1000000 < 100
              = [show (n `div` 1000000), "million", "and"]
                ++ upto999999 (n `mod` 1000000)
  | n>=1000000 && n `mod` 1000000 >= 100
              = [show (n `div` 1000000), "million"]
                ++ upto999999 (n `mod` 1000000)
    where
    upto999999 :: Integer -> [[Char]]
    upto999999 n
      | n<1000 = upto999 n
      | n>=1000 && n `mod` 1000 == 0
               = upto999 (n `div` 1000) ++ ["thousand"]
      | n>=1000 && n `mod` 1000 >  0 && n `mod` 1000 < 100
               = upto999 (n `div` 1000) ++ ["thousand", "and"]
                 ++ upto999 (n `mod` 1000)
      | n>=1000 && n `mod` 1000 >= 100
               = upto999 (n `div` 1000) ++ ["thousand"]
                 ++ upto999 (n `mod` 1000)
    upto999 :: Integer -> [[Char]]
    upto999 n
      | n<100 = upto99 n
      | 100<=n && n<1000 && n `mod` 100 == 0
              = upto99 (n `div` 100) ++ ["hundred"]
      | 100<=n && n<1000 && n `mod` 100 /= 0
              = upto99 (n `div` 100) ++ ["hundred", "and"]
                ++ upto99 (n `mod` 100)
    upto99 :: Integer -> [[Char]]
    upto99 n | n<=20 = [upto20!!fromInteger n]
             | n>20 && n `mod` 10 == 0
                     = [tens0to90!!fromInteger (n `div` 10)]
             | n>20 && n `mod` 10 /= 0
                     = [tens0to90!!fromInteger (n `div` 10)++"-",
                        upto20!!fromInteger (n `mod` 10)]
    upto20 = ["zero", "one", "two", "three", "four",
              "five", "six", "seven", "eight", "nine",
              "ten", "eleven", "twelve", "thirteen", "fourteen",
              "fifteen", "sixteen", "seventeen", "eighteen", "nineteen",
              "twenty"]
    tens0to90 = ["zero", "ten", "twenty", "thirty", "forty",
                 "fifty", "sixty", "seventy", "eighty", "ninety"]



is_alphanumeric :: Char -> Bool
is_alphanumeric ch = isletter ch || isdigit ch

isletter :: Char -> Bool
isletter ch = ((ch >= 'a') && (ch <= 'z')) || ((ch >= 'A') && (ch <= 'Z'))

isdigit :: Char -> Bool
isdigit ch = ((ch >= '0') && (ch <= '9'))
