""" Initial setup of the brightway2 project.

Creates project, imports databases and generates some useful data.
Should be run first.
"""
from pathlib import Path
import pickle
import json
import numpy as np
import pandas as pd
from brightway2 import *
from .utils import missing_useful_files, _check_result_dir


def setup_project(project_name, database_name, result_dir,
                  database_dir=None, overwrite_project=False,
                  overwrite_database=False, save_det_lci=True,
                  force_write_common_files=False,
                  default_bw2setup=True):
    """ Create project, import databases and generate common files as required

    Parameters
    -----------
    project_name : str
        Name of the brightway2 project in which to import data. If project does
        not exist, it will be created.
    database_name : str
        Name of the existing LCI database or to give to the LCI database being
        imported.
    result_dir : str
        Path to the directory where data used or generated by bw2preagg is saved.
    database_dir : str, default=None
        Path to directory with ecoSpold2 data to be imported, None if LCI
        database is not to be imported (i.e. if it exists already).
    overwrite_project : bool, default=False
        If True, then the existing project with the name project_name is deleted
        first, and all data is reimported for a clean slate import.
    overwrite_database : bool, default=False
        If True, then the existing LCI database with name database_name in the
        brightway2 project is deleted first, and LCI data is reimported
    force_write_common_files : bool, default=True
        If True, then the common files are generated even if they already exist
        at given location
    save_det_lci : bool, default=True
        If True, deterministic LCI arrays are saved in the deterministic subfolder
        of the result_dir
    default_bw2setup: bool, default=True
        If True, run bw2setup to include default elementary flows and LCIA methods

    Returns
    -------
    None
    """
    # Delete project on demand
    if overwrite_project and project_name in projects:
        print("Deleting preexisting project")
        projects.delete_project(project_name, delete_dir=True)
    # Create new project or switch to existing project
    projects.set_current(project_name)
    # Setup new project, if necessary
    if default_bw2setup:
        bw2setup()
    # Import LCI database, if necessary
    if not overwrite_database and database_name in databases:
        print("Importing of {} not necessary".format(database_name))
        pass
    else:
        importer = _prepare_import(database_dir, database_name)
        print("Importing {}".format(database_name))
        if database_name in databases and overwrite_database:
            print("Deleting {}".format(database_name))
            Database(database_name).delete()
            Database(database_name).deregister()
        importer.write_database()

    result_dir = Path(result_dir)
    result_dir.mkdir(parents=True, exist_ok=True)

    # Generate common data
    if missing_useful_files(result_dir) \
            or force_write_common_files\
            or save_det_lci:
        sacrificial_lca = get_sacrificial_LCA(database_name)
    if missing_useful_files(result_dir) or force_write_common_files:
        _generate_common_files(result_dir, database_name, sacrificial_lca)
    if save_det_lci:
        with open(result_dir / 'common_files' / 'ordered_activity_codes.json', "r") as f:
            activity_codes = json.load(f)
        _save_det_lci(result_dir, activity_codes, database_name, sacrificial_lca)


def _prepare_import(database_dir, database_name):
    """Check ecoSpolds can be imported

    Returns SingleOutputEcospold2Importer with strategies applied it is possible
    to write the data
    Will raise an error if something prevents it.
    """
    if not database_name:
        raise ValueError(
            "Cannot overwrite LCI database without database_name"
        )
    if not Path(database_dir).is_dir():
        raise ValueError(
            "database_dir does not exist, cannot import LCI data"
        )
    db_importer = SingleOutputEcospold2Importer(database_dir, database_name)
    db_importer.apply_strategies()
    if not db_importer.statistics()[2] == 0:
        raise ValueError(
            "{} unlinked exchanges when trying to import database".format(
                db_importer.statistics()[2]
            )
        )
    return db_importer


def _generate_common_files(result_dir, database_name, sacrificial_lca):
    """Generate and save common files used in subsequent steps"""
    print("\nGenerating common files")
    common_files_dir = Path(result_dir)/'common_files'
    common_files_dir.mkdir(exist_ok=True, parents=True)

    # Activity codes
    db = Database(database_name)
    activity_codes = [act.key[1] for act in db]
    activity_codes.sort()
    with open(common_files_dir/'ordered_activity_codes.json', "w") as f:
        json.dump(activity_codes, f, indent=4)

    sacrificial_lca.lci()

    # Save various attributes for eventual reuse in interpretation
    # LCA dicts, used to identify matrix coordinates
    with open(common_files_dir/'product_dict.pickle', "wb") as f:
        pickle.dump(sacrificial_lca.product_dict, f)
    with open(common_files_dir/'bio_dict.pickle', "wb") as f:
        pickle.dump(sacrificial_lca.biosphere_dict, f)
    with open(common_files_dir/'activity_dict.pickle', "wb") as f:
        pickle.dump(sacrificial_lca.activity_dict, f)

    # A matrix values, as coo
    with open(common_files_dir / "A_as_coo_scipy.pickle", "wb") as f:
        pickle.dump(sacrificial_lca.technosphere_matrix.tocoo(), f)
    df = pd.DataFrame(
        columns=['row', 'col', 'value'],
        data=np.concatenate(
            [
                sacrificial_lca.technosphere_matrix.tocoo().row.reshape(-1, 1),
                sacrificial_lca.technosphere_matrix.tocoo().col.reshape(-1, 1),
                sacrificial_lca.technosphere_matrix.tocoo().data.reshape(-1, 1)
            ], axis=1)
    )
    df.to_excel(common_files_dir / "A_as_coo.xlsx")

    # B matrix values, as coo
    with open(common_files_dir / "B_as_coo_scipy.pickle", "wb") as f:
        pickle.dump(sacrificial_lca.biosphere_matrix.tocoo(), f)
    df = pd.DataFrame(
        columns=['row', 'col', 'value'],
        data=np.concatenate(
            [
                sacrificial_lca.biosphere_matrix.tocoo().row.reshape(-1, 1),
                sacrificial_lca.biosphere_matrix.tocoo().col.reshape(-1, 1),
                sacrificial_lca.biosphere_matrix.tocoo().data.reshape(-1, 1)
            ], axis=1)
    )
    df.to_excel(common_files_dir / "B_as_coo.xlsx")

    # A row and col descriptions
    df = pd.DataFrame(columns=[
        'index',
        'activity name',
        'location',
        'ecoinvent activity uuid',
        'activity brightway2 code',
        'reference product name',
        'reference product amount',
        'reference product unit',
        'ecoinvent product uuid',
        'CPC',
        'EcoSpold01Categories',
        'ISIC rev.4 ecoinvent',
    ]
    )
    db_loaded = Database(database_name).load()
    rev_product_dict = {v:k for k, v in sacrificial_lca.product_dict.items()}
    for index in rev_product_dict:
        assert sacrificial_lca.activity_dict[rev_product_dict[index]]==index
        act_key = rev_product_dict[index]
        act = db_loaded[act_key]
        classifications = {c[0]: c[1] for c in act['classifications']}
        data = [
            index,
            act['name'],
            act['location'],
            act['filename'][0:36],
            act_key[1],
            act['reference product'],
            act['production amount'],
            act['unit'],
            act['filename'][37: 37+36],
            classifications.get('CPC', ''),
            classifications.get('EcoSpold01Categories', ''),
            classifications.get('ISIC rev.4 ecoinvent', ''),
        ]
        df.loc[index] = data
    df = df.set_index('index')
    df.to_excel(common_files_dir / "technosphere_description.xlsx")
    db_loaded = None
    df = None

    # B row and col descriptions
    df = pd.DataFrame(columns=[
        'index',
        'name',
        'unit',
        'compartment',
        'subcompartment',
        'type',
        'ecoinvent uuid',
        'brightway2 code',
    ]
    )
    db_loaded = Database('biosphere3').load()
    rev_bio_dict = {v:k for k, v in sacrificial_lca.biosphere_dict.items()}
    for index in rev_bio_dict:
        act_key = rev_bio_dict[index]
        act = db_loaded[act_key]
        cats = act['categories']
        subcat = cats[1] if len(cats) == 2 else ""
        data = [
            index,
            act['name'],
            act['unit'],
            cats[0],
            subcat,
            act['type'],
            act['code'],
            act_key[1]
        ]
        df.loc[index] = data
    df = df.set_index('index')
    df.to_excel(common_files_dir / "biosphere_description.xlsx")
    db_loaded = None
    df = None

    # CFs
    good_methods = [m for m in methods if "obsolete" not in str(m)]
    cfs = np.zeros(shape=(len(sacrificial_lca.biosphere_dict), len(good_methods)))
    for i, m in enumerate(good_methods):
        sacrificial_lca.switch_method(m)
        cfs[:, i] = sacrificial_lca.characterization_matrix.sum(axis=1).ravel()
    np.save(str(common_files_dir / "cfs.npy"), cfs)
    df = pd.DataFrame(columns=good_methods, data=cfs)
    df.to_excel(common_files_dir / "cfs.xlsx")

    # Mapping
    with open(common_files_dir/'IO_Mapping.pickle', "wb") as f:
        pickle.dump({v: k for k, v in mapping.items()}, f)


def _save_det_lci(result_dir, activity_codes, database_name, sacrificial_lca):
    """Deterministic LCI results"""
    print("\nGenerating deterministic results")
    result_dir = Path(_check_result_dir(result_dir))
    det_lci_dir = result_dir / "deterministic" / "LCI"
    det_lci_dir.mkdir(parents=True, exist_ok=True)
    sacrificial_lca.lci()
    for code in activity_codes:
        try:
            act = get_activity((database_name, code))
            sacrificial_lca.redo_lci({act: act.get('production amount', 1)})
            np.save(
                str(det_lci_dir / "{}.npy".format(code)),
                sacrificial_lca.inventory.sum(axis=1)
            )
        except Exception as err:
            print("******************")
            print(code)
            print(err)
            print("******************")


def get_sacrificial_LCA(database_name):
    """LCA object used to extract common file information"""
    collector_functional_unit = {
        act:act.get('production amount', 1)
        for act in Database(database_name)
    }
    return LCA(collector_functional_unit)

""" Initial setup of the brightway2 project.

Creates project, imports databases and generates some useful data.
Should be run first.
"""
from pathlib import Path
import pickle
import json
import numpy as np
import pandas as pd
from brightway2 import *
from .utils import missing_useful_files, _check_result_dir


def setup_project(project_name, database_name, result_dir,
                  database_dir=None, overwrite_project=False,
                  overwrite_database=False, save_det_lci=True,
                  force_write_common_files=False,
                  default_bw2setup=True):
    """ Create project, import databases and generate common files as required

    Parameters
    -----------
    project_name : str
        Name of the brightway2 project in which to import data. If project does
        not exist, it will be created.
    database_name : str
        Name of the existing LCI database or to give to the LCI database being
        imported.
    result_dir : str
        Path to the directory where data used or generated by bw2preagg is saved.
    database_dir : str, default=None
        Path to directory with ecoSpold2 data to be imported, None if LCI
        database is not to be imported (i.e. if it exists already).
    overwrite_project : bool, default=False
        If True, then the existing project with the name project_name is deleted
        first, and all data is reimported for a clean slate import.
    overwrite_database : bool, default=False
        If True, then the existing LCI database with name database_name in the
        brightway2 project is deleted first, and LCI data is reimported
    force_write_common_files : bool, default=True
        If True, then the common files are generated even if they already exist
        at given location
    save_det_lci : bool, default=True
        If True, deterministic LCI arrays are saved in the deterministic subfolder
        of the result_dir
    default_bw2setup: bool, default=True
        If True, run bw2setup to include default elementary flows and LCIA methods

    Returns
    -------
    None
    """
    # Delete project on demand
    if overwrite_project and project_name in projects:
        print("Deleting preexisting project")
        projects.delete_project(project_name, delete_dir=True)
    # Create new project or switch to existing project
    projects.set_current(project_name)
    # Setup new project, if necessary
    if default_bw2setup:
        bw2setup()
    # Import LCI database, if necessary
    if not overwrite_database and database_name in databases:
        print("Importing of {} not necessary".format(database_name))
        pass
    else:
        importer = _prepare_import(database_dir, database_name)
        print("Importing {}".format(database_name))
        if database_name in databases and overwrite_database:
            print("Deleting {}".format(database_name))
            Database(database_name).delete()
            Database(database_name).deregister()
        importer.write_database()

    result_dir = Path(result_dir)
    result_dir.mkdir(parents=True, exist_ok=True)

    # Generate common data
    if missing_useful_files(result_dir) \
            or force_write_common_files\
            or save_det_lci:
        sacrificial_lca = get_sacrificial_LCA(database_name)
    if missing_useful_files(result_dir) or force_write_common_files:
        _generate_common_files(result_dir, database_name, sacrificial_lca)
    if save_det_lci:
        with open(result_dir / 'common_files' / 'ordered_activity_codes.json', "r") as f:
            activity_codes = json.load(f)
        _save_det_lci(result_dir, activity_codes, database_name, sacrificial_lca)


def _prepare_import(database_dir, database_name):
    """Check ecoSpolds can be imported

    Returns SingleOutputEcospold2Importer with strategies applied it is possible
    to write the data
    Will raise an error if something prevents it.
    """
    if not database_name:
        raise ValueError(
            "Cannot overwrite LCI database without database_name"
        )
    if not Path(database_dir).is_dir():
        raise ValueError(
            "database_dir does not exist, cannot import LCI data"
        )
    db_importer = SingleOutputEcospold2Importer(database_dir, database_name)
    db_importer.apply_strategies()
    if not db_importer.statistics()[2] == 0:
        raise ValueError(
            "{} unlinked exchanges when trying to import database".format(
                db_importer.statistics()[2]
            )
        )
    return db_importer


def _generate_common_files(result_dir, database_name, sacrificial_lca):
    """Generate and save common files used in subsequent steps"""
    print("\nGenerating common files")
    common_files_dir = Path(result_dir)/'common_files'
    common_files_dir.mkdir(exist_ok=True, parents=True)

    # Activity codes
    db = Database(database_name)
    activity_codes = [act.key[1] for act in db]
    activity_codes.sort()
    with open(common_files_dir/'ordered_activity_codes.json', "w") as f:
        json.dump(activity_codes, f, indent=4)

    sacrificial_lca.lci()

    # Save various attributes for eventual reuse in interpretation
    # LCA dicts, used to identify matrix coordinates
    with open(common_files_dir/'product_dict.pickle', "wb") as f:
        pickle.dump(sacrificial_lca.product_dict, f)
    with open(common_files_dir/'bio_dict.pickle', "wb") as f:
        pickle.dump(sacrificial_lca.biosphere_dict, f)
    with open(common_files_dir/'activity_dict.pickle', "wb") as f:
        pickle.dump(sacrificial_lca.activity_dict, f)

    # A matrix values, as coo
    with open(common_files_dir / "A_as_coo_scipy.pickle", "wb") as f:
        pickle.dump(sacrificial_lca.technosphere_matrix.tocoo(), f)
    df = pd.DataFrame(
        columns=['row', 'col', 'value'],
        data=np.concatenate(
            [
                sacrificial_lca.technosphere_matrix.tocoo().row.reshape(-1, 1),
                sacrificial_lca.technosphere_matrix.tocoo().col.reshape(-1, 1),
                sacrificial_lca.technosphere_matrix.tocoo().data.reshape(-1, 1)
            ], axis=1)
    )
    df.to_excel(common_files_dir / "A_as_coo.xlsx")

    # B matrix values, as coo
    with open(common_files_dir / "B_as_coo_scipy.pickle", "wb") as f:
        pickle.dump(sacrificial_lca.biosphere_matrix.tocoo(), f)
    df = pd.DataFrame(
        columns=['row', 'col', 'value'],
        data=np.concatenate(
            [
                sacrificial_lca.biosphere_matrix.tocoo().row.reshape(-1, 1),
                sacrificial_lca.biosphere_matrix.tocoo().col.reshape(-1, 1),
                sacrificial_lca.biosphere_matrix.tocoo().data.reshape(-1, 1)
            ], axis=1)
    )
    df.to_excel(common_files_dir / "B_as_coo.xlsx")

    # A row and col descriptions
    df = pd.DataFrame(columns=[
        'index',
        'activity name',
        'location',
        'ecoinvent activity uuid',
        'activity brightway2 code',
        'reference product name',
        'reference product amount',
        'reference product unit',
        'ecoinvent product uuid',
        'CPC',
        'EcoSpold01Categories',
        'ISIC rev.4 ecoinvent',
    ]
    )
    db_loaded = Database(database_name).load()
    rev_product_dict = {v:k for k, v in sacrificial_lca.product_dict.items()}
    for index in rev_product_dict:
        assert sacrificial_lca.activity_dict[rev_product_dict[index]]==index
        act_key = rev_product_dict[index]
        act = db_loaded[act_key]
        classifications = {c[0]: c[1] for c in act['classifications']}
        data = [
            index,
            act['name'],
            act['location'],
            act['filename'][0:36],
            act_key[1],
            act['reference product'],
            act['production amount'],
            act['unit'],
            act['filename'][37: 37+36],
            classifications.get('CPC', ''),
            classifications.get('EcoSpold01Categories', ''),
            classifications.get('ISIC rev.4 ecoinvent', ''),
        ]
        df.loc[index] = data
    df = df.set_index('index')
    df.to_excel(common_files_dir / "technosphere_description.xlsx")
    db_loaded = None
    df = None

    # B row and col descriptions
    df = pd.DataFrame(columns=[
        'index',
        'name',
        'unit',
        'compartment',
        'subcompartment',
        'type',
        'ecoinvent uuid',
        'brightway2 code',
    ]
    )
    db_loaded = Database('biosphere3').load()
    rev_bio_dict = {v:k for k, v in sacrificial_lca.biosphere_dict.items()}
    for index in rev_bio_dict:
        act_key = rev_bio_dict[index]
        act = db_loaded[act_key]
        cats = act['categories']
        subcat = cats[1] if len(cats) == 2 else ""
        data = [
            index,
            act['name'],
            act['unit'],
            cats[0],
            subcat,
            act['type'],
            act['code'],
            act_key[1]
        ]
        df.loc[index] = data
    df = df.set_index('index')
    df.to_excel(common_files_dir / "biosphere_description.xlsx")
    db_loaded = None
    df = None

    # CFs
    good_methods = [m for m in methods if "obsolete" not in str(m)]
    cfs = np.zeros(shape=(len(sacrificial_lca.biosphere_dict), len(good_methods)))
    for i, m in enumerate(good_methods):
        sacrificial_lca.switch_method(m)
        cfs[:, i] = sacrificial_lca.characterization_matrix.sum(axis=1).ravel()
    np.save(str(common_files_dir / "cfs.npy"), cfs)
    df = pd.DataFrame(columns=good_methods, data=cfs)
    df.to_excel(common_files_dir / "cfs.xlsx")

    # Mapping
    with open(common_files_dir/'IO_Mapping.pickle', "wb") as f:
        pickle.dump({v: k for k, v in mapping.items()}, f)


def _save_det_lci(result_dir, activity_codes, database_name, sacrificial_lca):
    """Deterministic LCI results"""
    print("\nGenerating deterministic results")
    result_dir = Path(_check_result_dir(result_dir))
    det_lci_dir = result_dir / "deterministic" / "LCI"
    det_lci_dir.mkdir(parents=True, exist_ok=True)
    sacrificial_lca.lci()
    for code in activity_codes:
        try:
            act = get_activity((database_name, code))
            sacrificial_lca.redo_lci({act: act.get('production amount', 1)})
            np.save(
                str(det_lci_dir / "{}.npy".format(code)),
                sacrificial_lca.inventory.sum(axis=1)
            )
        except Exception as err:
            print("******************")
            print(code)
            print(err)
            print("******************")


def get_sacrificial_LCA(database_name):
    """LCA object used to extract common file information"""
    collector_functional_unit = {
        act:act.get('production amount', 1)
        for act in Database(database_name)
    }
    return LCA(collector_functional_unit)

Initial setup of the brightway2 project.  Creates project, imports databases and generates some useful data. Should be run first. Create project, import databases and generate common files as required      Parameters     -----------     project_name : str         Name of the brightway2 project in which to import data. If project does         not exist, it will be created.     database_name : str         Name of the existing LCI database or to give to the LCI database being         imported.     result_dir : str         Path to the directory where data used or generated by bw2preagg is saved.     database_dir : str, default=None         Path to directory with ecoSpold2 data to be imported, None if LCI         database is not to be imported (i.e. if it exists already).     overwrite_project : bool, default=False         If True, then the existing project with the name project_name is deleted         first, and all data is reimported for a clean slate import.     overwrite_database : bool, default=False         If True, then the existing LCI database with name database_name in the         brightway2 project is deleted first, and LCI data is reimported     force_write_common_files : bool, default=True         If True, then the common files are generated even if they already exist         at given location     save_det_lci : bool, default=True         If True, deterministic LCI arrays are saved in the deterministic subfolder         of the result_dir     default_bw2setup: bool, default=True         If True, run bw2setup to include default elementary flows and LCIA methods      Returns     -------     None # Delete project on demand # Create new project or switch to existing project # Setup new project, if necessary # Import LCI database, if necessary # Generate common data Check ecoSpolds can be imported      Returns SingleOutputEcospold2Importer with strategies applied it is possible     to write the data     Will raise an error if something prevents it. Generate and save common files used in subsequent steps # Activity codes # Save various attributes for eventual reuse in interpretation # LCA dicts, used to identify matrix coordinates # A matrix values, as coo # B matrix values, as coo # A row and col descriptions # B row and col descriptions # CFs # Mapping Deterministic LCI results LCA object used to extract common file information