"""Module containing models representing patients and their data.

The Model layer is responsible for the 'business logic' part of
the software.

Patients' data is held in an inflammation table (2D array) where each
row contains inflammation data for a single patient taken over a number
of days and each column represents a single day across all patients.
"""

import numpy as np


def load_csv(filename):
    """Load a Numpy array from a CSV

    :param filename: Filename of CSV to load
    """
    return np.loadtxt(fname=filename, delimiter=',')


def daily_mean(data):
    """Calculate the daily mean of a 2D inflammation data array.

    :param data: A 2D data array with inflammation data (each row
    contains measurements for a single patient across all days).
    :returns: An array of mean values of measurements for each day.
    """
    return np.mean(data, axis=0)


def daily_max(data):
    """Calculate the daily max of a 2D inflammation data array.

    :param data: A 2D data array with inflammation data (each row
    contains measurements for a single patient across all days).
    :returns: An array of maximum values of measurements for each day.
    """
    return np.max(data, axis=0)


def daily_min(data):
    """Calculate the daily min of a 2D inflammation data array.

    :param data: A 2D data array with inflammation data (each row
    contains measurements for a single patient across all days).
    :returns: An array of minimum values of measurements for each day.
    """
    return np.min(data, axis=0)


def patient_normalise(data):
    """
    Normalise patient data from a 2D inflammation data array.

    NaN values are ignored, and normalised to 0.
    """
    if not isinstance(data, np.ndarray):
        raise TypeError('Inflammation values must be of type ndarray')
    if np.any(data < 0):
        raise ValueError('Inflammation values should not be negative')
    max = np.nanmax(data, axis=1)
    with np.errstate(invalid='ignore', divide='ignore'):
        normalised = data / max[:, np.newaxis]
    normalised[np.isnan(normalised)] = 0
    normalised[normalised < 0] = 0
    return normalised


# TODO(lesson-design) Implement data persistence
# TODO(lesson-design) Add Doctor class


def attach_names(data, names):
    """Create datastructure containing patient records."""
    assert len(data) == len(names)
    output = []
    for name, row in zip(names, data):
        output.append({"name": name,
                       "data": list(row)})
    return output


class Observation:
    def __init__(self, day, value):
        self.day = day
        self.value = value

    def __str__(self):
        return str(self.value)


class Person:
    def __init__(self, name):
        self.name = name

    def __str__(self):
        return self.name


class Doctor(Person):
    """A doctor in an inflammation study."""
    def __init__(self, name, patients=[]):
        super().__init__(name)
        self.observations = []
        self.patients = patients


class Patient(Person):
    """A patient in an inflammation study."""
    def __init__(self, name, observations=[]):
        super().__init__(name)
        self.observations = observations

    def add_observation(self, value, day=None):
        if day is None:
            try:
                day = self.observations[-1].day + 1

            except IndexError:
                day = 0

        new_observation = Observation(value, day)

        self.observations.append(new_observation)
        return new_observation

    @property
    def last_observation(self):
        return self.observations[-1]
