#!/usr/bin/env python
# -*- coding: utf-8 -*-
from copy import deepcopy
from collections import defaultdict

DEBUG = False
##############################################################################
# COUNTRY & SPECIES SPECIFIC CONSTANTS FOR THE CARBON LIFE CYCLE PREDICTION
#
# NB! Be careful to change the code as well if you happen to change any
# of the strings in the dictionaries below, be they keys or in the values
#
##############################################################################
# Carbon released in cutting kg/ha
CUTTING_C = 10.
# For the given moisture content, the energy production (MWh) from 1 kg
# biomass (http://tinyurl.com/yaexxf7)
ENERGY_FROM_MASS = (.35, 0.00314)
# How much of the tree dry biomass is carbon
# stock species, assortment translation table
# NB! make sure the keys in this match the ones in lexicon, otherwise
# you'll be getting KeyErrors
KEY_TRANSLATION = {(1.0, 1): ('pine', 'log'),
                   (1.0, 2): ('pine', 'pulp'),
                   (2.0, 1): ('spruce', 'log'),
                   (2.0, 2): ('spruce', 'pulp'),
                   (3.0, 1): ('birch', 'log'),
                   (3.0, 2): ('birch', 'pulp'),
                   (4.0, 1): ('birch', 'log'),
                   (4.0, 2): ('birch', 'pulp'),
                   (5.0, 1): ('birch', 'log'),
                   (5.0, 2): ('birch', 'pulp'),
                   (6.0, 1): ('birch', 'log'),
                   (6.0, 2): ('birch', 'pulp'),
                   (7.0, 1): ('birch', 'log'),
                   (7.0, 2): ('birch', 'pulp'),
                   (8.0, 1): ('pine', 'log'),
                   (8.0, 2): ('pine', 'pulp'),
                   (9.0, 1): ('birch', 'log'),
                   (9.0, 2): ('birch', 'pulp'),
                  }
# Keep these in line with the content of KEY_TRANSLATION
SPS = ['pine', 'spruce', 'birch']
SP_DENSITY = {'pine': 475., 'spruce': 405., 'birch': 600.}
# Product life cycle definitions
# Carbon released in the manufacturing process as a share of the product
# carbon mass
C_MANUF = {'sawn timber':.032,
           'recycling': .48,
           'plywood': .069,
           'mechanical pulp': .48,
           'chemical pulp': .13}
# How the mass of the raw stock coming from the forest is converted to actual
# products, proportions of the mass
LOG2PROD = {'pine': {'sawn timber':1., 'plywood':0.,
                     'mechanical pulp':0., 'chemical pulp':0.},
            'spruce': {'sawn timber':.84, 'plywood':.15,
                       'mechanical pulp':.01, 'chemical pulp':0.},
            'birch': {'sawn timber':.09, 'plywood':.91,
                      'mechanical pulp':0., 'chemical pulp':0.},
           }
PULP2PROD = {'pine': {'sawn timber':.09, 'plywood':0.,
                      'mechanical pulp':0., 'chemical pulp':.91},
            'spruce': {'sawn timber':.05, 'plywood':0.,
                       'mechanical pulp':.67, 'chemical pulp':.28},
            'birch': {'sawn timber':0., 'plywood':0.,
                      'mechanical pulp':0.09, 'chemical pulp':.91},
           }
# Internal conversions of the products; i.e., of the raw material coming
# to the factory gate to be converted to the given product (keys), how much
# of it actually goes into each of the given product classes (keys and values
# of the innermost dictionary)
PROD_CONVERSION = \
    {'sawn timber': {'sawn timber':.44, 'plywood':0.,
                     'mechanical pulp':0., 'chemical pulp':.36,
                     'energy':.19},
     'plywood': {'sawn timber':0., 'plywood':.38,
                 'mechanical pulp':0., 'chemical pulp':.28,
                 'energy':.34},
     'mechanical pulp': {'sawn timber':0., 'plywood':0.,
                         'mechanical pulp':.93, 'chemical pulp':0.,
                         'energy':.07},
     'chemical pulp': {'sawn timber':0., 'plywood':0.,
                        'mechanical pulp':0., 'chemical pulp':.46,
                        'energy':.54},
     }
# Half lifes for different product categories
HL = {'long': 50., 'medium-long': 16., 'medium-short': 4., 'short': 1.}
# life time distribution of different product categories, mass proportions
# of the total product mass in different life time categories
PRODUCT_LIFETIME = {'sawn timber': {'long': .5, 'medium-long': .25,
                                    'medium-short': 0., 'short': .25},
                    'plywood': {'long': .05, 'medium-long': .35,
                                'medium-short': 0., 'short': .6},
                    'fiber': {'long': 0., 'medium-long': 0.,
                              'medium-short': .33, 'short': .67},
                   }
# Recycling percentage for 'fiber' product categorcy
FIBER_RECYCLED = .6
##############################################################################

def energy_conversion(mass, mwh2c_ref, c_in_kg):
    """
    Carbon release from energy production from the wood material

    mass -- mass used in energy production
    """
    energy = mass * (1. + ENERGY_FROM_MASS[0]) * ENERGY_FROM_MASS[1]
    c_ref = energy * mwh2c_ref
    c_rel = c_in_kg * mass
    c_rel_energy = c_rel - c_ref
    return c_rel, c_rel_energy

class ProductLifeCycle(object):
    """
    This class represents the product lifecycle of wood cut in a cutting and
    the carbon released in different phases of the lifecycle from the wood
    material
    """

    def __init__(self, area, harv_res, carbon_flux, mwh2c_ref,
                 carbon_in_biomass):
        """
        area -- the area of the cut stand, ha
        harv_res -- harvest result dictionary
        mwh2c_ref -- reference value for how much carbon (kg) is released in
                     energy production per MWh. Deduced from the carbon
                     released when converting biomass to energy
        carbon_in_biomass -- share of the carbon in kg biomass
        """
        assert area > 0.
        assert len(harv_res) > 0
        self._area = area
        self._stock = defaultdict(float)
        #debug
        tot_vol = 0.
        for key, vol in harv_res['Volume'].iteritems():
            my_key = KEY_TRANSLATION[key]
            self._stock[my_key] += vol
            #debug
            tot_vol += vol
        self._mwh2c_ref = mwh2c_ref
        self._c_in_kg = carbon_in_biomass
        self._sawn_timber = 0.
        self._plywood = 0.
        self._chemical_pulp = 0.
        self._mechanical_pulp = 0.
        self._recycled_pulp = 0.
        self.c_flux = carbon_flux
        #debug
        if DEBUG:
            self._debug_mass_c_rel = 0.
            print '*********'
            print 'Total volume:', tot_vol

    def _cutting(self):
        """
        Carbon released during the cutting operation
        """
        c_rel = self._area * CUTTING_C
        self.c_flux['Carbon_flux'][0] += c_rel
        self.c_flux['Carbon_flux_energy'][0] += c_rel

    def _energy_conversion(self, mass, yr):
        """
        Carbon release from energy production from the wood material

        mass -- mass used in energy production
        yr -- the time stamp for the carbon release from energy production
        """
        c_rel, c_rel_energy = energy_conversion(mass, self._mwh2c_ref,
                                                self._c_in_kg)
        self.c_flux['Carbon_flux_energy'][yr] += c_rel_energy
        self.c_flux['Carbon_flux'][yr] += c_rel
        #debug
        if DEBUG:
            self._debug_mass_c_rel += c_rel

    def _half_life(self, product, yr):
        """
        Gives the amount of product left after year yr given the original mass

        product -- the product category key
        yr -- the timestep
        """
        pr = self._product
        to_energy = 0.
        if product != 'fiber':
            # all to_energy to energy production
            for lt in HL:
                old_mass = self._hl[product][lt]
                if old_mass == 0.0:
                    continue
                new_mass = pr[product][lt] * 0.5**(yr / HL[lt])
                self._hl[product][lt] = new_mass
                to_energy += old_mass - new_mass
        else:
            recycled = 0.
            # NB! make sure these match the life times used in carbon_constants
            for lt in ('long', 'medium-long', 'medium-short', 'short'):
                # part is recycled
                old_mass = self._hl[product][lt]
                if old_mass == 0.0 and lt != 'short':
                    continue
                new_mass = pr[product][lt] * 0.5**(yr / HL[lt])
                diff = old_mass - new_mass
                recycled += FIBER_RECYCLED * diff
                to_energy += (1. - FIBER_RECYCLED) * diff
                if lt == 'short':
                    self._hl[product][lt] = new_mass + recycled
                else:
                    self._hl[product][lt] = new_mass
            # carbon release from the recycling process
            c_rel = self._c_in_kg * C_MANUF['recycling'] * recycled
            self.c_flux['Carbon_flux'][yr] += c_rel
            self.c_flux['Carbon_flux_energy'][yr] += c_rel
        return to_energy

    def _manufacturing(self):
        """
        Distribution of raw stock to different end products.
        Carbon released during the manufacture process
        """
        # distribute volume to carbon masses in different product categories
        for key, vol in self._stock.iteritems():
            sp, assortment = key
            mass = SP_DENSITY[sp] * vol
            self._process_raw_material(assortment, sp, mass)
        # carbon release from the manufacturing process
        c_rel = C_MANUF['sawn timber'] * self._sawn_timber +\
                C_MANUF['plywood'] * self._plywood +\
                C_MANUF['mechanical pulp'] * self._mechanical_pulp +\
                C_MANUF['chemical pulp'] * self._chemical_pulp
        c_rel = self._c_in_kg * c_rel
        self.c_flux['Carbon_flux'][0] += c_rel
        self.c_flux['Carbon_flux_energy'][0] += c_rel
        # allocation to different lifespan categories of product classes
        self._product = defaultdict(dict)
        tot_fiber = self._mechanical_pulp + self._chemical_pulp
        for pr, lts in PRODUCT_LIFETIME.iteritems():
            for lifetime, value in lts.iteritems():
                if pr == 'sawn timber':
                    self._product[pr][lifetime] = value * self._sawn_timber
                elif pr == 'plywood':
                    self._product[pr][lifetime] = value * self._plywood
                elif pr == 'fiber':
                    self._product[pr][lifetime] = value * tot_fiber
        self._hl = deepcopy(self._product)

    def _process_raw_material(self, material, sp, mass):
        """
        Manufacturing process description for logged round wood

        material -- raw material type, log or pulp wood
        sp -- tree species
        mass -- carbon mass, kg
        """
        to_energy = 0.
        if material == 'log':
            prod = LOG2PROD[sp]
        else:
            prod = PULP2PROD[sp]
        for product in prod:
            prod_mass = prod[product] * mass
            for real_prod, prop in PROD_CONVERSION[product].iteritems():
                if prop == 0.:
                    continue
                final_mass = prop * prod_mass
                if real_prod == product:
                    self._allocate_mass(real_prod, final_mass)
                elif real_prod != 'energy':
                    sub_prods = PROD_CONVERSION[real_prod]
                    for sub_prod, sub_prop in sub_prods.iteritems():
                        sub_mass = final_mass * sub_prop
                        if sub_prod != 'energy':
                            self._allocate_mass(sub_prod, sub_mass)
                        else:
                            to_energy += sub_mass
                else:
                    to_energy += final_mass
        self._energy_conversion(to_energy, 0)

    def _allocate_mass(self, product, prod_mass):
        if product == 'sawn timber':
            self._sawn_timber += prod_mass
        elif product == 'plywood':
            self._plywood += prod_mass
        elif product == 'mechanical pulp':
            self._mechanical_pulp += prod_mass
        elif product == 'chemical pulp':
            self._chemical_pulp += prod_mass

    def _yr_release(self, yr):
        """
        Carbon release per year during the product lifecycle

        yr -- n'th year within the lifecycle
        """
        to_energy = 0.
        hl = self._half_life
        to_energy += hl('sawn timber', yr)
        to_energy += hl('plywood', yr)
        to_energy += hl('fiber', yr)
        self._energy_conversion(to_energy, yr)

    def c_release(self, yrs):
        """
        Carbon release during given years

        yrs -- timespan for the C release prediction
        """
        self._cutting()
        self._manufacturing()
        for yr in range(1, int(yrs)):
            self._yr_release(yr)
        # carbon contained in the remaining products, stamped to the
        # yrs + 1 in its entirety, not taking into account the remaining
        # recycled fibre manufacturing carbon releases
        rem_mass = 0.
        for prod, cats in self._hl.iteritems():
            for cat, mass in cats.iteritems():
                rem_mass += mass
        self._energy_conversion(rem_mass, int(yrs) + 1)
        res = []
        res_energy = []
        # debug
        if DEBUG:
            mass = 0.
            for key, val in self._stock.iteritems():
                mass += SP_DENSITY[key[0]] * val
            c_in = self._c_in_kg * mass
            c_released = 0.
            for yr, crel in self.c_flux['Carbon_flux'].iteritems():
                c_released += crel
            c_energy = 0.
            for yr, crel in self.c_flux['Carbon_flux_energy'].iteritems():
                c_energy += crel
            print 'in:', c_in, 'out diff:', c_in - self._debug_mass_c_rel
            print 'all out:', c_released
            print 'energy compensated:', c_energy
            pass


if __name__ == '__main__':
    pass
