"""Data Preparation

Calculates and joins parameters required for pygypsy to a plot table

"""
#pylint: disable=no-member
from __future__ import division
import logging
import pandas as pd
from copy import deepcopy

from pygypsy.stand_density_factor import (
    estimate_sdf_aw,
    estimate_sdf_sb,
    estimate_sdf_sw,
    estimate_sdf_pl,
)
from pygypsy.density import (
    estimate_density_aw,
    estimate_density_sw,
    estimate_density_sb,
    estimate_density_pl,
)
from pygypsy.site_index import (
    get_site_indices_from_dominant_species,
    _estimate_site_index
)
from pygypsy.utils import (
    _get_gypsy_valid_species,
    _log_loop_progress,
    _generate_fplot_dict,
    _reclassify_and_sort_species,
)
from pygypsy.asaCompileAgeGivenSpSiHt import (
    computeTreeAge,
    ComputeGypsyTreeHeightGivenSiteIndexAndTotalAge,
    ComputeGypsySiteIndex,
)
from pygypsy.exceptions import MinimumAgeError, ProportionsSumError


LOGGER = logging.getLogger(__name__)


def _populate_species_dict_with_indices(species_dict, estimated_site_indices):
    """Fill the dictionary with estimated SIs

    Fill all the SIs to avoid IFs and loops. Some of them will not be
    used.

    :param species_dict: dictionary used to store params for all species
    :param estimated_site_indices: array of estimated site indices from
                                   get_species_site_indices function

    ..note: the dominant species in the estiamted_site_indices uses its
    observed site index

    """
    local_species_dict = deepcopy(species_dict)

    for species, site_index in estimated_site_indices.items():
        local_species_dict[species]['SI'] = site_index

    return local_species_dict


# TODO: this is a bit deep in the stack to use the pandas data structure
# should refactor in future. instead pass tph and bph as args
def populate_species_dict(partial_species_list,
                          sorted_species_abbrev_perc_tuples_list,
                          dominant_species=None, row=None,
                          dominant_species_current_age=None,
                          dominant_species_current_height=None):
    """Fill partial fplot

    Given fplot with proportion filled out, add age, top height, basal area,
    density

    :param partial_species_list:
    :param sorted_species_abbrev_perc_tuples_list:
    :param dominant_species:
    :param dominant_species_current_age:
    :param dominant_species_current_height:

    """
    species_dict = deepcopy(partial_species_list)
    outer_sorted_species_perc_list = sorted_species_abbrev_perc_tuples_list
    for species in outer_sorted_species_perc_list:
        # TODO: raise NoDominantSpeciesError and catch it where called instead of break
        # confirmed with julianno
        if species[1] == 0:
            break
        # TODO: this is always true on first pass of the loop
        elif species[0] == dominant_species[0]:
            species_dict[species[0]]['tage'] = dominant_species_current_age
            species_dict[species[0]]['topHeight'] = dominant_species_current_height
            species_dict[species[0]]['N'] = row['TPH'] * species[1] / 100
            species_dict[species[0]]['BA'] = row['BPH'] * species[1] / 100
            x_si = ComputeGypsySiteIndex(
                species[0],
                species_dict[species[0]]['topHeight'],
                0,
                species_dict[species[0]]['tage']
            )
            species_dict[species[0]]['bhage'] = x_si[0]
            # if, after re-arranging the proportions, dom species is another
            # one then we need to re-estimate everything  even for the new
            # dominant species
        else:
            si_sp = species_dict[species[0]]['SI']
            species_dict[species[0]]['PCT'] = species[1]
            species_dict[species[0]]['tage'] = computeTreeAge(
                species[0],
                treeHt=dominant_species_current_height,
                treeSi=si_sp,
                maxTreeAge=450
            )
            # density based on the proportion of the species
            species_dict[species[0]]['N'] = row['TPH'] * species[1]/100
            # Basal area from the species proportion as well
            species_dict[species[0]]['BA'] = row['BPH'] * species[1]/100
            # calling the ComputeGypsySiteIndex function, estimate bhage
            x_si = ComputeGypsySiteIndex(
                species[0], dominant_species_current_height, 0,
                species_dict[species[0]]['tage']
            )
            species_dict[species[0]]['bhage'] = x_si[0]
    return species_dict


def _prep_row(row, minimum_age=25):
    '''Estimate properties required for gypsy simulation

    :param row: row of data frame
    :param minimum age: age below which plot should be skipped

    :raises ProportionsSumError: if species proportions do not add to 1
    :raises MinimumAgeError: if plot is less than minimum age

    '''
    species_abbrev_percent_list = [
        (row['SP1'], row['PCT1']),
        (row['SP2'], row['PCT2']),
        (row['SP3'], row['PCT3']),
        (row['SP4'], row['PCT4']),
        (row['SP5'], row['PCT5'])
    ]
    plot_dominant_species = row['SP1']
    dominant_species_current_age = row['AGE']
    dominant_species_current_height = row['HD']

    check_prop = sum(zip(*species_abbrev_percent_list)[1])
    if check_prop != 100:
        raise ProportionsSumError(
            'Species proportions do not add to 1: %s' % check_prop
        )

    if dominant_species_current_age < minimum_age:
        msg = ('Dominant species age is less than minimum age %s;'
               'skipping plot %s') % (minimum_age, row['id_l1'])
        raise MinimumAgeError(msg)

    site_index = _estimate_site_index(
        plot_dominant_species,
        dominant_species_current_age,
        dominant_species_current_height
    )

    temp_dominant_species = _get_gypsy_valid_species(plot_dominant_species)
    gypsy_site_indices = get_site_indices_from_dominant_species(
        temp_dominant_species,
        site_index
    )
    empty_species_dict = _generate_fplot_dict()
    species_dict = _populate_species_dict_with_indices(empty_species_dict,
                                                       gypsy_site_indices)

    outer_sorted_species_perc_list, outer_species_perc_dict = \
        _reclassify_and_sort_species(species_abbrev_percent_list)

    species_dict['Aw']['PCT'] = outer_species_perc_dict['Aw']
    species_dict['Pl']['PCT'] = outer_species_perc_dict['Pl']
    species_dict['Sw']['PCT'] = outer_species_perc_dict['Sw']
    species_dict['Sb']['PCT'] = outer_species_perc_dict['Sb']

    dominant_species = outer_sorted_species_perc_list[0]

    species_dict = populate_species_dict(
        species_dict, outer_sorted_species_perc_list,
        dominant_species=dominant_species,
        row=row,
        dominant_species_current_age=dominant_species_current_age,
        dominant_species_current_height=dominant_species_current_height
    )

    density_aw = species_dict['Aw']['N']
    density_sw = species_dict['Sw']['N']
    density_pl = species_dict['Pl']['N']
    density_sb = species_dict['Sb']['N']
    tage_aw = species_dict['Aw']['tage']
    tage_sw = species_dict['Sw']['tage']
    tage_pl = species_dict['Pl']['tage']
    tage_sb = species_dict['Sb']['tage']
    bhage_aw = species_dict['Aw']['bhage']
    bhage_sw = species_dict['Sw']['bhage']
    bhage_pl = species_dict['Pl']['bhage']
    bhage_sb = species_dict['Sb']['bhage']
    y2bh_aw = tage_aw - bhage_aw
    y2bh_sw = tage_sw - bhage_sw
    y2bh_pl = tage_pl - bhage_pl
    y2bh_sb = tage_sb - bhage_sb
    site_index_aw = species_dict['Aw']['SI']
    site_index_sw = species_dict['Sw']['SI']
    site_index_pl = species_dict['Pl']['SI']
    site_index_sb = species_dict['Sb']['SI']
    basal_area_aw = species_dict['Aw']['BA']
    basal_area_sb = species_dict['Sb']['BA']
    basal_area_sw = species_dict['Sw']['BA']
    basal_area_pl = species_dict['Pl']['BA']

    top_height_aw = ComputeGypsyTreeHeightGivenSiteIndexAndTotalAge(
        'Aw', site_index_aw, tage_aw
    )
    top_height_sb = ComputeGypsyTreeHeightGivenSiteIndexAndTotalAge(
        'Sb', site_index_sb, tage_sb
    )
    top_height_pl = ComputeGypsyTreeHeightGivenSiteIndexAndTotalAge(
        'Pl', site_index_pl, tage_pl
    )
    top_height_sw = ComputeGypsyTreeHeightGivenSiteIndexAndTotalAge(
        'Sw', site_index_sw, tage_sw
    )
    density_bh_aw, sdf_aw0 = estimate_sdf_aw(
        'Aw', site_index_aw, bhage_aw, density_aw
    )
    density_bh_sb, sdf_sb0 = estimate_sdf_sb(
        'Sb', site_index_sb, tage_sb, density_sb
    )
    density_bh_sw, sdf_sw0 = estimate_sdf_sw(
        'Sw', site_index_sw, tage_sw, sdf_aw0, density_sw
    )
    density_bh_pl, sdf_pl0 = estimate_sdf_pl(
        'Pl', site_index_pl, tage_pl,
        sdf_aw0, sdf_sw0, sdf_sb0, density_pl
    )

    initial_density_aw = estimate_density_aw(
        sdf_aw0, 0, site_index_aw
    )
    initial_density_sb = estimate_density_sb(
        sdf_sb0, 0, site_index_sb
    )
    initial_density_sw = estimate_density_sw(
        sdf_sw0, sdf_aw0, 0, site_index_sw
    )
    initial_density_pl = estimate_density_pl(
        sdf_aw0, sdf_sw0, sdf_sb0, sdf_pl0, 0, site_index_pl
    )

    plot_properties = {
        'SI_Aw': site_index_aw,
        'SI_Sw': site_index_sw,
        'SI_Pl': site_index_pl,
        'SI_Sb': site_index_sb,
        'N_Aw': density_aw,
        'N_Sw': density_sw,
        'N_Pl': density_pl,
        'N_Sb': density_sb,
        'y2bh_Aw': y2bh_aw,
        'y2bh_Sw': y2bh_sw,
        'y2bh_Pl': y2bh_pl,
        'y2bh_Sb': y2bh_sb,
        'tage_Aw': tage_aw,
        'tage_Sw': tage_sw,
        'tage_Pl': tage_pl,
        'tage_Sb': tage_sb,
        'BA_Aw': basal_area_aw,
        'BA_Sw': basal_area_sw,
        'BA_Pl': basal_area_pl,
        'BA_Sb': basal_area_sb,
        'SDF_Aw': sdf_aw0,
        'SDF_Sw': sdf_sw0,
        'SDF_Pl': sdf_pl0,
        'SDF_Sb': sdf_sb0,
        'N0_Aw': initial_density_aw,
        'N0_Sb': initial_density_sb,
        'N0_Sw': initial_density_sw,
        'N0_Pl': initial_density_pl,
        'StumpDOB_Aw': species_dict['Aw']['StumpDOB'],
        'StumpDOB_Sb': species_dict['Sb']['StumpDOB'],
        'StumpDOB_Sw': species_dict['Sw']['StumpDOB'],
        'StumpDOB_Pl': species_dict['Pl']['StumpDOB'],
        'StumpHeight_Aw': species_dict['Aw']['StumpHeight'],
        'StumpHeight_Sb': species_dict['Sb']['StumpHeight'],
        'Stumpheight_Sw': species_dict['Sw']['StumpHeight'],
        'StumpHeight_Pl': species_dict['Pl']['StumpHeight'],
        'TopDib_Aw': species_dict['Aw']['TopDib'],
        'TopDib_Sb': species_dict['Sb']['TopDib'],
        'TopDib_Sw': species_dict['Sw']['TopDib'],
        'TopDib_Pl': species_dict['Pl']['TopDib'],
        'topHeight_Aw': top_height_aw,
        'topHeight_Sw': top_height_sw,
        'topHeight_Sb': top_height_sb,
        'topHeight_Pl': top_height_pl
    }

    return plot_properties


def prep_standtable(data, **kwargs):
    '''Define site_index of all other species given the dominant species

    The site index is only defined for the dominant species in a plot. This
    function uses the dominant species and its site index to estimate the site
    indices for other species. It estimates other values and returns a data
    frame of all of the values for all plots input.

    estimates site_index Dom from inventory Age and HD
    this site_index is used to estimate the site index of the other species
    for example:
    - FD is dom species take HD and Age
    - assume FD is Sw generate site_index for Sw
    - get the SW site_index
    - calculate the SIs for other species from the conversion formulas

    :param data: input data frame
    :param kwargs: additional arguments to :func:`_prep_row`
    '''
    plot_dict = {}
    n_rows = data.shape[0]

    for i, row in data.iterrows():
        _log_loop_progress(i, n_rows)
        plot_id = row['id_l1']

        try:
            prepped_plot = _prep_row(row, **kwargs)
            plot_dict[plot_id] = prepped_plot
        except MinimumAgeError as err:
            LOGGER.error(err.message)
        except ProportionsSumError as err:
            LOGGER.error(err.message)
        except Exception as err: #pylint: disable=broad-except
            LOGGER.critical('Unhandled error: %s', err.message)

    plot_df = pd.DataFrame(plot_dict)
    plot_df = plot_df.transpose()

    return plot_df
