-- |
-- Module      : Alergias
-- Description : Código de las alergias.
-- Copyright   : Exercitium (03-06-14)
-- License     : GPL-3
-- Maintainer  : JoseA.Alonso@gmail.com
-- 
-- __Código de las alergias__
-- 
-- Para la determinación de las alergia se utiliza los siguientes
-- códigos para los alérgenos:
--
-- >   Huevos ........   1
-- >   Cacahuetes ....   2
-- >   Mariscos ......   4
-- >   Fresas ........   8
-- >   Tomates .......  16
-- >   Chocolate .....  32
-- >   Polen .........  64
-- >   Gatos ......... 128
-- 
-- Así, si Juan es alérgico a los cacahuetes y al chocolate, su
-- puntuación es 34 (es decir, 2+32).
-- 
-- Los alérgenos se representan mediante el siguiente tipo de dato
-- 
-- >  data Alergeno = Huevos
-- >                | Cacahuetes
-- >                | Mariscos
-- >                | Fresas
-- >                | Tomates
-- >                | Chocolate
-- >                | Polen
-- >                | Gatos
-- >                deriving (Enum, Eq, Show, Bounded)
-- 
-- Definir la función
-- 
-- > alergias :: Int -> [Alergeno]
-- 
-- tal que __(alergias n)__ es la lista de alergias correspondiente a una
-- puntuación n. Por ejemplo,
-- 
-- >>> alergias 0
-- []
-- >>> alergias 1
-- [Huevos]
-- >>> alergias 2
-- [Cacahuetes]
-- >>> alergias 8
-- [Fresas]
-- >>> alergias 3
-- [Huevos,Cacahuetes]
-- >>> alergias 5
-- [Huevos,Mariscos]
-- >>> alergias 248
-- [Fresas,Tomates,Chocolate,Polen,Gatos]
-- >>> alergias 255
-- [Huevos,Cacahuetes,Mariscos,Fresas,Tomates,Chocolate,Polen,Gatos]
-- >>> alergias 509
-- [Huevos,Mariscos,Fresas,Tomates,Chocolate,Polen,Gatos]

module Alergias where

import Data.List (subsequences)
import Test.QuickCheck

-- | Tipo de alergenos.
data Alergeno = Huevos
              | Cacahuetes
              | Mariscos
              | Fresas
              | Tomates
              | Chocolate
              | Polen
              | Gatos
              deriving (Enum, Eq, Show, Bounded)

-- | 1ª definición (usando números binarios).
alergias :: Int -> [Alergeno]
alergias n = 
  [toEnum x | (y,x) <- zip (int2bin n) [0..7]
            , y == 1]

-- | __(int2bin n)__ es la representación binaria del número n. Por
-- ejemplo,
-- 
-- >>> int2bin 10
-- [0,1,0,1]
--
-- ya que 10 = 0*1 + 1*2 + 0*4 + 1*8
int2bin :: Int -> [Int]
int2bin n | n < 2     = [n]
          | otherwise = n `rem` 2 : int2bin (n `div` 2)

-- | 2ª definición (sin usar números binarios).
alergias2 :: Int -> [Alergeno]
alergias2 n = map toEnum (aux n 0)
  where aux 0 _                = []
        aux _ a | a > 7        = []
        aux 1 a                = [a]
        aux m a | rem m 2 == 0 = aux (div m 2) (1+a)
                | otherwise    = a : aux (div m 2) (1+a) 

-- | 3ª definición (con combinaciones).
alergias3 :: Int -> [Alergeno]
alergias3 n = 
  [a | (a,c) <- zip alergenos codigos
     , c `elem` descomposicion n]

-- | __alergenos__ es la lista de los alergenos
alergenos :: [Alergeno]
alergenos = [Huevos,Cacahuetes,Mariscos,Fresas,Tomates,Chocolate,Polen,Gatos]

-- | __codigos__ es la lista de los códigos de los alergenos.
codigos :: [Int]
codigos = [2^x| x <- [0..7]]
    
-- | __(descomposicion n)__ es la descomposición de n (módulo 256) como sumas
-- de potencias de 2. Por ejemplo,
-- 
-- >>> descomposicion 3
-- [1,2]
-- >>> descomposicion 5
-- [1,4]
-- >>> descomposicion 248
-- [8,16,32,64,128]
-- >>> descomposicion 255
-- [1,2,4,8,16,32,64,128]
-- >>> descomposicion 509
-- [1,4,8,16,32,64,128]
descomposicion :: Int -> [Int]                     
descomposicion n = 
  head [xs | xs <- subsequences codigos
           , sum xs == n `mod` 256]

-- | Comprobación de la equivalencia de las definiciones de 'alergia'.
--
-- >>> quickCheck prop_equiv_alergias
-- +++ OK, passed 100 tests.
prop_equiv_alergias :: (NonNegative Int) -> Bool
prop_equiv_alergias (NonNegative n) =
  all (== alergias n )
      [f n | f <- [ alergias2
                  , alergias3
                  ]]