# coding: utf-8
# Solución ALS (c) 2019 Baltasar  MIT License <jbgarcia@uvigo.es>


"""
    1. (4pts.) La clase Ciudadano alberga los años más significativos de
    un ciudadano de un país dado. Se construye con unos apellidos, un nombre
    y una lista de tres números: el año de nacimiento,
    el año del primer empleo, el año de jubilación y el de defunción.
    Los métodos de esta clase serán
    exactamente __init__(), __getattr__() y __str__().
    Aún así, será capaz de responder a la petición de varias “propiedades”:
    annos devuelve una nueva lista con todos los años, nacimiento
    devolverá el año de nacimiento, primer_empleo el del primer empleo,
    jubilacion la de jubilación, y defuncion la de defunción.
    El “método” calcula_annos_vida() calcula el número de años
    entre el nacimiento y la defunción.
    El “método” calcula_annos_activos() calcula la diferencia
    entre los años del primer empleo y la jubilación.
    El método __str__() devolverá la información en el formato
    <apellidos>, <nombre>: <nacimiento> - <defuncion>(<primer_empleo> - <jubilacion>),
    como por ejemplo: Díaz de Vivar, Rodrigo: 1043-1099(1065-1099).

    c1 = Ciudadano(“Díaz de Vivar”, “Rodrigo”, [1043, 1065, 1099, 1099])
	print(c1.annos[0], c1.primer_empleo, c1.annos[2], c1.defuncion)	# 1043 1065 1099 1099
	print(c1.calcula_annos_vida())					# 56
	print(c1.calcula_annos_activos())				# 34
	print(c1)	                           # Díaz de Vivar, Rodrigo: 1043-1099(1065-1099)

    2. (1pts.) Cree los métodos to_json(f) y from_json(f).
    El primero escribe la codificación JSON del objeto en un archivo f,
    la segunda toma un archivo f con contenido en formato JSON
    y crea un objeto con dicho contenido.

    3. (4pts.) Cree la clase Censo que almacena ciudadanos en una lista.
    Esta clase admite un parámetro en el constructor:
    un objeto de la clase Ciudadano o una lista con los objetos Ciudadano.
    Tendrá también el acceso de solo lectura [i],
    que devolverán el ciudadano i-ésimo, y la función len()
    el número total de  ciudadanos.
    El método __str_₍) devolverá toda la
    información con cada ciudadano en su propia línea.
    El método add(c) permitirá añadir un nuevo ciudadano,
    mientras que los métodos calcula_media_vida()
    y calcula_media_activos() calcularán la media de los años de vida
    y los años activos, respectivamente.

    4. (1pts.) Incluya en la clase Censo los métodos
    calcula_media_vida() y calcula_media_activos()
    que calcularán la media de los años de vida y los años activos,
    respectivamente.
    Deben hacerlo empleando la función reduce(f,l,i) del paquete estándar functtols.
"""


import io
import unittest
import json
from functools import reduce


class Ciudadano:
	def __init__(self, apellidos, nombre, annos):
		self._apellidos = apellidos
		self._nombre = nombre
		self._annos = annos
		
	def __getattr__(self, s):
		toret = ""
		
		if s == "apellidos":
			toret = self._apellidos
		elif s == "nombre":
			toret = self._nombre
		elif s == "annos":
			toret = list(self._annos)
		elif s == "nacimiento":
			toret = self._annos[0]
		elif s == "primer_empleo":
			toret = self._annos[1]
		elif s == "jubilacion":
			toret = self._annos[2]
		elif s == "defuncion":
			toret = self._annos[3]
		elif s == "calcula_annos_vida":
			toret = lambda: self.annos[3] - self.annos[0]
		elif s == "calcula_annos_activos":
			toret = lambda: self.annos[2] - self.annos[1]
			
		return toret
	
	def to_json(self, f):
		json.dump(self.__dict__, f)
			
	def from_json(self, f):
		toret = Ciudadano("", "", [])
		toret.__dict__ = json.read(f)
		
		return toret
		
	def __str__(self):
		return str.format("{}, {}: {}-{}({}-{})",
						  self.apellidos,
						  self.nombre,
						  self.nacimiento,
						  self.defuncion,
						  self.primer_empleo,
						  self.jubilacion)

	
class Censo:
	def __init__(self, l):
		if l:
			if isinstance(l, list):
				self._ciudadanos = l
			elif isinstance(l, Ciudadano):
				self._ciudadanos = [l]
				
	def add(self, c):
		self._ciudadanos.append(c)
		
	def calcula_media_activos(self):
		toret = reduce(
						lambda acc, c2: acc + c2.calcula_annos_activos(),  
						self._ciudadanos,
						0)
		return toret / len(self._ciudadanos)
	
	def calcula_media_vida(self):
		toret = reduce(
						lambda acc, c2: acc + c2.calcula_annos_vida(),  
						self._ciudadanos,
						0)
		return toret / len(self._ciudadanos)
				
	def __getitem__(self, it):
		return self._ciudadanos[it]
	
	def __len__(self):
		return len(self._ciudadanos)

	def __str__(self):
		toret = ""
		
		for c in self._ciudadanos:
			toret += str(c) + '\n'
			
		return toret

 
class TestCiudadano(unittest.TestCase):
    def setUp(self):
        self.c1 = Ciudadano("Diaz de Vivar", "Rodrigo", [1043, 1065, 1099, 1099])
        self.datos_c1 = "{'_apellidos':'Diaz de Vivar','_nombre':'Rodrigo','_annos':[1043,1065,1099,1099]}"
         
    def testNombre(self):
        self.assertEqual("Rodrigo", self.c1.nombre)
         
    def testApellidos(self):
        self.assertEqual("Diaz de Vivar", self.c1.apellidos)
         
    def testNacimiento(self):
        self.assertEqual(1043, self.c1.nacimiento)
         
    def testDefuncion(self):
        self.assertEqual(1099, self.c1.defuncion)
         
    def testJubilacion(self):
        self.assertEqual(1099, self.c1.jubilacion)
         
    def testPrimerEmpleo(self):
        self.assertEqual(1065, self.c1.primer_empleo)
         
    def testCiudadano(self):
        self.assertEqual("Diaz de Vivar, Rodrigo: 1043-1099(1065-1099)", str(self.c1))
         
    def testAnnosVida(self):
        self.assertEqual(56, self.c1.calcula_annos_vida())
         
    def testAnnosActivos(self):
        self.assertEqual(34, self.c1.calcula_annos_activos())
         
    def testToJson(self):
        with io.StringIO() as f:
            self.c1.to_json(f)
            self.assertEqual(TestCiudadano.prepara_resultado(self.datos_c1), TestCiudadano.prepara_resultado(f.getvalue().strip()))
 
    @staticmethod
    def prepara_resultado(s):
        return str(s).strip().replace('\'', '"').replace(' ', "")
     
     
class TestCenso(unittest.TestCase):
    def setUp(self):
        self.c1 = Ciudadano("Diaz de Vivar", "Rodrigo", [1043, 1065, 1099, 1099])
        self.c2 = Ciudadano("Kane", "Roger", [1840, 1860, 1880, 1890])
        self.censo = Censo([self.c1])
         
    def testLen(self):
        self.assertEqual(len(self.censo._ciudadanos), len(self.censo))
         
    def testStr(self):
        expected = "\n".join([str(c) for c in self.censo._ciudadanos])
        self.assertEqual(expected, str(self.censo).strip())
         
    def testAdd(self):
        self.censo.add(self.c2)
        self.assertEqual(2, len(self.censo))
        self.assertEqual(self.c2, self.censo[1])
         
    def testAccess(self):
        self.assertEqual(self.c1, self.censo[0])
         
    def testCalculaMediaVida(self):
        media_vida = 0
         
        for c in self.censo._ciudadanos:
            media_vida += c.calcula_annos_vida()
             
        media_vida /= len(self.censo._ciudadanos)
             
        self.assertEqual(media_vida, self.censo.calcula_media_vida())
         
    def testCalculaMediaActivos(self):
        media_activos = 0
         
        for c in self.censo._ciudadanos:
            media_activos += c.calcula_annos_activos()
             
        media_activos /= len(self.censo._ciudadanos)
             
        self.assertEqual(media_activos, self.censo.calcula_media_activos())
         
if __name__ == "__main__":
    unittest.main()
