#!/usr/bin/env python
# encoding: utf-8
"""
Title: harvest

Harvesting operations

*harvest.py*

Copyright (c) 2007 Simosol Oy. Distributed under the GNU General Public License 2.0.

NOTE:
    All Python operation models share the same set of input parameters. When
    implementing new operation model, remember to assign the following list
    of parameters as the function parameters:
        data - data by level
        variables - operation variable values
        parameters - operation parameter values
        par_table_val - values retrieved from a parameter table
        cash_flow_table - cash_flow structure
        cash_flow - container for returning cash flow (sum)
        results - container (numpy array) for returning operation results
        warnings - container for returning warnings
        errors - container for returning errors
"""

# Imports
from harvestdata import HarvestData
from harvestop import HarvestOperation
from constants import STANDLABEL

############################################################
##  Operation functions, callable from outside of module
############################################################

def bioenergy_thinning(data, variables, parameters, par_table_val,
                       cash_flow_table, cash_flow, results, restype,
                       warnings, errors, price_cache, stem_cache):
    """
    Bioenergy thinning. Implementation of the model is the same as in
    low_thinning, only operation name is different.
    """
    return thinning(data, variables, parameters, par_table_val,
                        cash_flow_table, cash_flow, results, restype,
                        warnings, errors, price_cache, stem_cache)

def clearcut(data, variables, parameters, par_table_val,
             cash_flow_table, cash_flow, results, restype,
             warnings, errors, price_cache, stem_cache):
    """
    Remove all trees except a number of reserve trees from a stand.
    """
    # check that operation data actually exists
    for level, ldata in data.iteritems():
        if ldata is None:
            errors.append("The level %s doesn't have data!")
            return -1
    hd = HarvestData('clearcut', data, variables, parameters,
                     par_table_val, cash_flow_table, price_cache)
    hd.set_stump_inclusion()
    hop = HarvestOperation(hd, stem_cache)
    # Iterate through all trees in the data from smallest to biggest
    for dcind in hd.cdata['removal_order']:
        hop.buck(dcind)
        props = hop.reduce_prop_for_reserve_trees(dcind, 1.0)
        hop.cut_dclass_trees(dcind, props)
    totrem = hop.store_harvest_results(results, cash_flow, restype=restype)
    return 1

def covertree_position(data, variables, parameters, par_table_val,
                         cash_flow_table, cash_flow, results, restype,
                         warnings, errors, price_cache, stem_cache):
    """
    Remove all trees except a number of covertrees from a stand.
    """
    return seedtree_position(data, variables, parameters,
                             par_table_val, cash_flow_table,
                             cash_flow, results, restype, warnings, errors,
                             price_cache, stem_cache)

def estimate_stand_value(data, variables, parameters, par_table_val,
                         cash_flow_table, cash_flow, results, restype, warnings,
                         errors, price_cache, stem_cache):
    """
    buck each tree of a stand and calculate the total value in given currency.
    """
    # check that operation data actually exists
    for level, ldata in data.iteritems():
        if ldata is None:
            errors.append("The level %s doesn't have data!")
            return -1
    hd = HarvestData('standvalue', data, variables, parameters,
                     par_table_val, cash_flow_table, price_cache)
    hd.cdata['min_stump_diam'] = 0.0
    hd.cdata['stump_excl_num'] = 0.0
    hd.cdata['biomass_incl_prop'] = 1.0
    hd.cdata['stump_incl_prop'] = 1.0
    hop = HarvestOperation(hd, stem_cache)
    for dcind in hd.cdata['removal_order']:
        hop.buck(dcind)
        hop.cut_dclass_trees(dcind, (1.0, 1.0), trackchanges=False)
    totrem = hop.store_stand_value_results(results)
    return 1

def estimate_tree_value(data, variables, parameters, par_table_val,
                         cash_flow_table, cash_flow, results, restype, warnings,
                         errors, price_cache, stem_cache):
    """
    buck each tree of a stand and calculate the total value in given currency.
    """
    # check that operation data actually exists
    for level, ldata in data.iteritems():
        if ldata is None:
            errors.append("The level %s doesn't have data!")
            return -1
    hd = HarvestData('standvalue', data, variables, parameters,
                     par_table_val, cash_flow_table, price_cache)
    hd.cdata['min_stump_diam'] = 0.0
    hd.cdata['stump_excl_num'] = 0.0
    hd.cdata['biomass_incl_prop'] = 1.0
    hd.cdata['stump_incl_prop'] = 1.0
    hop = HarvestOperation(hd, stem_cache)
    for dcind in hd.cdata['removal_order']:
        hop.buck(dcind)
        hop.cut_dclass_trees(dcind, (1.0, 1.0), trackchanges=False)
    totrem = hop.store_tree_value_results(results)
    return 1

def first_thinning(data, variables, parameters, par_table_val,
                   cash_flow_table, cash_flow, results, restype, warnings,
                   errors, price_cache, stem_cache):
    """
    First thinning. Implementation of the model is the same as in low_thinning,
    only operation name is different
    """
    return thinning(data, variables, parameters, par_table_val,
                    cash_flow_table, cash_flow, results, restype, warnings,
                    errors, price_cache, stem_cache)

def remove_seedtrees(data, variables, parameters, par_table_val,
                     cash_flow_table, cash_flow, results, restype, warnings,
                     errors, price_cache, stem_cache):
    """
    remove the seedtrees from a stand.
    """
    # check that operation data actually exists
    for level, ldata in data.iteritems():
        if ldata is None:
            errors.append("The level %s doesn't have data!")
            return -1
    hd = HarvestData('removeseedtree', data, variables, parameters,
                     par_table_val, cash_flow_table, price_cache,
                     keepemptystrata=True)
    hop = HarvestOperation(hd, stem_cache)
    ind = hd.ind
    strata = hd.stratumdata
    for dcind in hd.cdata['removal_order']:
        tree, istratum, sp = hop.get_dclass_info(dcind)
        if strata[istratum, ind['stratum_seedtree']] == 1.0:
            hop.buck(dcind)
            hop.cut_dclass_trees(dcind, (1.0, 1.0), biomass=False)
    removedvolumemerch = hop.store_harvest_results(results, cash_flow,
                                                  biomass=False, tagmods=False)
    # Abort the operation in case accumulated volume was too small
    # NB! removedvolumemerch is the total merchantable (log+pulpwood)
    # volume removed per hectare
    if removedvolumemerch < hd.cdata['min_vol_accumulation_remove_seedtrees']:
        return -2
    return 1

def seedtree_position(data, variables, parameters, par_table_val,
                      cash_flow_table, cash_flow, results, restype, warnings,
                      errors, price_cache, stem_cache):
    """
    Remove all trees except a number of seedtrees from a stand.
    """
    # check that operation data actually exists
    for level, ldata in data.iteritems():
        if ldata is None:
            errors.append("The level %s doesn't have data!")
            return -1

    hd = HarvestData('seedtree', data, variables, parameters,
                     par_table_val, cash_flow_table, price_cache)
    hd.set_stump_inclusion()
    hop = HarvestOperation(hd, stem_cache)
    # iteration from smallest to biggest trees
    for dcind in hd.cdata['removal_order']:
        hop.buck(dcind)
        props = hop.reduce_prop_for_seed_and_reserve_trees(dcind, 1.0)
        hop.cut_dclass_trees(dcind, props)
    removedvolumemerch = hop.store_harvest_results(results, cash_flow,
                                                   restype=restype)
    # Abort the operation in case accumulated volume was too small
    # NB! removedvolumemerch is the total merchantable (log+pulpwood)
    # volume removed per hectare
    if removedvolumemerch < hd.cdata['min_vol_accumulation_seedtree_position']:
        return -2
    return 1

def sheltertree_position(data, variables, parameters, par_table_val,
                         cash_flow_table, cash_flow, results, restype, warnings,
                         errors, price_cache, stem_cache):
    """
    Remove all trees except a number of sheltertrees from a stand.
    """
    return seedtree_position(data, variables, parameters,
                             par_table_val, cash_flow_table,
                             cash_flow, results, restype, errors,
                             price_cache, stem_cache)

def strip_cutting(data, variables, parameters, par_table_val,
                  cash_flow_table, cash_flow, results, restype, errors,
                  price_cache, stem_cache):
    """
    Remove all trees from narrow strip shaped stand. The trees in adjasent
    stands will seed the new tree generation of the strip cutting area.
    """
    # Assign seedtree_density to 0 and add another table value
    par_table_val =[[[0]], par_table_val[0]]
    return seedtree_position(data, variables, parameters,
                             par_table_val, cash_flow_table,
                             cash_flow, results, restype, warnings, errors,
                             price_cache, stem_cache)

def thinning(data, variables, parameters, par_table_val,
             cash_flow_table, cash_flow, results, restype, warnings, errors,
             price_cache, stem_cache):
    """
    Executes a thinning in a stand. Remove a given portion of classes from
    stand's diameter distribution, starting from the smallest or biggest ones.
    The thinning type (low or upper thinning) is defined using parameters.
    """
    # check that operation data actually exists
    for level, ldata in data.iteritems():
        if ldata is None:
            errors.append("The level %s doesn't have data!")
            return -1

    hd = HarvestData('thinning', data, variables, parameters,
                     par_table_val, cash_flow_table, price_cache)
    hd.set_track_removal()
    # Thinning based on basal area
    targetvar='BA'
    hd.set_thinning_control_param(targetvar)
    hd.set_stump_inclusion()
    hd.set_d_cut_all_below()
    hop = HarvestOperation(hd, stem_cache)
    # Buck all tree stems and remove stems from tracks
    for dcind in hd.cdata['removal_order']:
        prop = hd.cdata['track_removal']
        hop.buck(dcind)
        hop.cut_dclass_trees(dcind, (prop, prop))
    # Update BA_to_remove when stems from tracks are removed
    hd.update_to_remove()
    # remove diameter classes from both ends of the distribution
    hop.cut_from_top_and_bottom()
    # Finally store the cumulative volumes, values etc. into result data
    removedvolumemerch = hop.store_harvest_results(results, cash_flow,
                                                   restype=restype)
    # Abort the operation in case accumulated volume was too small
    # NB! removedvolumemerch is the total merchantable (log+pulpwood)
    # volume removed per hectare
    if removedvolumemerch < hd.cdata['min_vol_accumulation']:
        return -2
    return 1

def first_thinning_stem_number(data, variables, parameters,
           par_table_val, cash_flow_table, cash_flow, results, restype,
           warnings, errors, price_cache, stem_cache):
    """
    Executes a stem number based first thinning in a stand. Remove a given
    portion of classes from stand's diameter distribution, starting from the
    smallest or biggest ones.
    The thinning type (low or upper thinning) is defined using parameters.
    """
    # check that operation data actually exists
    for level, ldata in data.iteritems():
        if ldata is None:
            errors.append("The level %s doesn't have data!")
            return -1

    hd = HarvestData('first_thinning_stem_number', data, variables, parameters,
                     par_table_val, cash_flow_table, price_cache)
    hd.set_track_removal()
    # Thinning based on stem number
    targetvar='N'
    hd.set_thinning_control_param(targetvar)
    hd.set_stump_inclusion()
    hd.set_d_cut_all_below()
    hop = HarvestOperation(hd, stem_cache)
    # Buck all tree stems and remove stems from tracks
    for dcind in hd.cdata['removal_order']:
        prop = hd.cdata['track_removal']
        hop.buck(dcind)
        hop.cut_dclass_trees(dcind, (prop, prop))
    # Update N_to_remove when stems from tracks are removed
    hd.update_to_remove()
    # remove diameter classes from both ends of the distribution
    hop.cut_from_top_and_bottom()
    # Finally store the cumulative volumes, values etc. into result data
    removedvolumemerch = hop.store_harvest_results(results, cash_flow,
                                                   restype=restype)
    # Abort the operation in case accumulated volume was too small
    # NB! removedvolumemerch is the total merchantable (log+pulpwood)
    # volume removed per hectare
    if removedvolumemerch < hd.cdata['min_vol_accumulation']:
        return -2
    return 1

def thinning_with_removed_volume(data, variables, parameters,
            par_table_val, cash_flow_table, cash_flow, results, restype,
            warnings, errors, price_cache, stem_cache):
    """
    Executes a thinning in a stand. Remove a given portion of classes from
    stand's diameter distribution, starting from the smallest or biggest ones.
    The thinning type (low or upper thinning) is defined using parameters.
    """
    #Check if V_harvested or remaining_BA are given
    if data[STANDLABEL][0][8]==0 or (data[STANDLABEL][0][8]>0 and data[STANDLABEL][0][12]>0):
        # Basic low thinning. If removed volume is 0 or both removed volume and remaining 
        # basal area are greater then 0, basic low_thinning is used (Remaining_upper and 
        # Remaining_lower in thinning model replaced with remaining_ba)
        return thinning(data, variables, parameters, par_table_val,
                            cash_flow_table, cash_flow, results, restype,
                            warnings, errors, price_cache,
                            stem_cache)
    else:
        # check that operation data actually exists
        for level, ldata in data.iteritems():
            if ldata is None:
                errors.append("The level %s doesn't have data!")
                return -1
        hd = HarvestData('thinning_with_removed_volume', data, variables,
                         parameters, par_table_val, cash_flow_table,
                         price_cache)
        hd.set_track_removal()
        # Thinning based on volume of timber (commercially usable wood)
        targetvar='V_com'
        hd.set_thinning_control_param(targetvar)
        hd.set_stump_inclusion()
        hd.set_d_cut_all_below()
        hop = HarvestOperation(hd, stem_cache)
        # Buck all tree stems
        for dcind in hd.cdata['removal_order']:
            hop.buck(dcind)
        # Calculate scaling factor
        svol = hop.hdata.result['stratum_vol']
        scale = hop.assign_scale(svol)
        # Remove stems from tracks
        for dcind in hd.cdata['removal_order']:
            prop = hd.cdata['track_removal']
            hop.cut_dclass_trees(dcind, (prop, prop), scale=scale)
        # Update V_to_remove when stems from tracks are removed
        # NB! With wide track_width and narrow track_distance, and small
        # accumulated timber volume  the whole removed volume might be already
        # removed here! Check the thinning model parameters in the operation
        # model chain.
        cut_timber = hop.cut_timber
        hd.update_to_remove(cut_timber)
        # remove diameter classes from both ends of the distribution
        hop.cut_from_top_and_bottom(scale)
        # Finally store the cumulative volumes, values etc. into result data
        hop.store_harvest_results(results, cash_flow, restype=restype)
        return 1

def selection_cut(data, variables, parameters, par_table_val,
                  cash_flow_table, cash_flow, results, restype,warnings,
                  errors, price_cache, stem_cache):
    """
    Basic selection cutting model derived from basic harvest model.
    """
    # check that operation data actually exists
    for level, ldata in data.iteritems():
        if ldata is None:
            errors.append("The level %s doesn't have data!")
            return -1
    hd = HarvestData('selection_cut', data, variables, parameters,
                     par_table_val, cash_flow_table, price_cache)
    hd.set_track_removal()
    # Thinning based on basal area
    targetvar='BA'
    hd.set_thinning_control_param(targetvar)
    hd.set_stump_inclusion()
    hd.set_d_cut_all_above()
    hop = HarvestOperation(hd, stem_cache)
    # Buck all tree stems and remove stems from tracks
    for dcind in hd.cdata['removal_order']:
        prop = hd.cdata['track_removal']
        hop.buck(dcind)
        hop.cut_dclass_trees(dcind, (prop, prop))
    # Update BA_to_remove when stems from tracks are removed
    hd.update_to_remove()
    # remove diameter classes from both ends of the distribution
    hop.cut_evenly()
    # Finally store the cumulative volumes, values etc. into result data
    removedvolumemerch = hop.store_harvest_results(results, cash_flow,
                                                   restype=restype)
    # Abort the operation in case accumulated volume was too small
    # NB! removedvolumemerch is the total merchantable (log+pulpwood)
    # volume removed per hectare
    if removedvolumemerch < hd.cdata['min_vol_accumulation']:
        return -2
    return 1

def wood_assortments_plot(data, variables, parameters, par_table_val,
                         cash_flow_table, cash_flow, results, restype,
                         warnings, errors, price_cache, stem_cache):
    """
    buck each tree of a stand and calculate the total value in given currency.
    """
    # check that operation data actually exists
    for level, ldata in data.iteritems():
        if ldata is None:
            errors.append("The level %s doesn't have data!")
            return -1

    hd = HarvestData('wood_assortments', data, variables, parameters,
                     par_table_val, cash_flow_table, price_cache)
    hd.cdata['min_stump_diam'] = 0.0
    hd.cdata['stump_excl_num'] = 0.0
    hd.cdata['biomass_incl_prop'] = 0.0
    hd.cdata['stump_incl_prop'] = 0.0
    hop = HarvestOperation(hd, stem_cache)
    for dcind in hd.cdata['removal_order']:
        hop.buck(dcind)
        hop.cut_dclass_trees(dcind, (1.0, 1.0), volres=True, biomass=False,
                             trackchanges=False)
    totrem = hop.store_wood_assortment_results(results)
    return 1
