# Copyright (c) 2018, INRIA
# Copyright (c) 2018, University of Lille
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright notice, this
#   list of conditions and the following disclaimer.
#
# * Redistributions in binary form must reproduce the above copyright notice,
#   this list of conditions and the following disclaimer in the documentation
#   and/or other materials provided with the distribution.
#
# * Neither the name of the copyright holder nor the names of its
#   contributors may be used to endorse or promote products derived from
#   this software without specific prior written permission.

# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

import os
import sys
import logging

from functools import reduce
from powerapi.exception import PowerAPIException
from powerapi.cli.parser import MainParser, ComponentSubParser
from powerapi.cli.parser import store_true
from powerapi.cli.parser import BadValueException, MissingValueException
from powerapi.cli.parser import BadTypeException, BadContextException
from powerapi.cli.parser import UnknowArgException
from powerapi.report_model import HWPCModel, PowerModel, FormulaModel, ControlModel
from powerapi.database import MongoDB, CsvDB, InfluxDB, OpenTSDB
from powerapi.puller import PullerActor
from powerapi.pusher import PusherActor


def enable_log(arg, val, args, acc):
    acc[arg] = logging.DEBUG
    return args, acc

def check_csv_files(files):
    return reduce(lambda acc, f: acc and os.access(f, os.R_OK), files.split(','), True)


def extract_file_names(arg, val, args, acc):
    acc[arg] = val.split(',')
    return args, acc


class CommonCLIParser(MainParser):

    def __init__(self):
        MainParser.__init__(self)

        self.add_argument('v', 'verbose', flag=True, action=enable_log, default=logging.NOTSET,
                          help='enable verbose mode')
        self.add_argument('s', 'stream', flag=True, action=store_true, default=False, help='enable stream mode')

        subparser_mongo_input = ComponentSubParser('mongodb')
        subparser_mongo_input.add_argument('u', 'uri', help='sepcify MongoDB uri')
        subparser_mongo_input.add_argument('d', 'db', help='specify MongoDB database name', )
        subparser_mongo_input.add_argument('c', 'collection', help='specify MongoDB database collection')
        subparser_mongo_input.add_argument('n', 'name', help='specify puller name', default='puller_mongodb')
        subparser_mongo_input.add_argument('m', 'model', help='specify data type that will be storen in the database',
                                           default='HWPCReport')
        self.add_component_subparser('input', subparser_mongo_input,
                                     help_str='specify a database input : --db_output database_name ARG1 ARG2 ... ')

        subparser_csv_input = ComponentSubParser('csv')
        subparser_csv_input.add_argument('f', 'files',
                                         help='specify input csv files with this format : file1,file2,file3',
                                         action=extract_file_names, default=[], check=check_csv_files,
                                         check_msg='one or more csv files couldn\'t be read')
        subparser_csv_input.add_argument('m', 'model', help='specify data type that will be storen in the database',
                                         default='HWPCReport')
        subparser_csv_input.add_argument('n', 'name', help='specify puller name', default='puller_csv')
        self.add_component_subparser('input', subparser_csv_input,
                                     help_str='specify a database input : --db_output database_name ARG1 ARG2 ... ')

        subparser_mongo_output = ComponentSubParser('mongodb')
        subparser_mongo_output.add_argument('u', 'uri', help='sepcify MongoDB uri')
        subparser_mongo_output.add_argument('d', 'db', help='specify MongoDB database name')
        subparser_mongo_output.add_argument('c', 'collection', help='specify MongoDB database collection')
                                            
        subparser_mongo_output.add_argument('m', 'model', help='specify data type that will be storen in the database',
                                            default='PowerReport')
        subparser_mongo_output.add_argument('n', 'name', help='specify puller name', default='pusher_mongodb')
        self.add_component_subparser('output', subparser_mongo_output,
                                     help_str='specify a database output : --db_output database_name ARG1 ARG2 ...')

        subparser_csv_output = ComponentSubParser('csv')
        subparser_csv_output.add_argument('d', 'directory',
                                          help='specify directory where where output  csv files will be writen')
        subparser_csv_output.add_argument('m', 'model', help='specify data type that will be storen in the database',
                                          default='PowerReport')
        subparser_csv_output.add_argument('n', 'name', help='specify puller name', default='pusher_csv')
        self.add_component_subparser('output', subparser_csv_output,
                                     help_str='specify a database input : --db_output database_name ARG1 ARG2 ... ')

        subparser_influx_output = ComponentSubParser('influxdb')
        subparser_influx_output.add_argument('u', 'uri', help='sepcify InfluxDB uri')
        subparser_influx_output.add_argument('d', 'db', help='specify InfluxDB database name')
        subparser_influx_output.add_argument('p', 'port', help='specify InfluxDB connection port', type=int)
        subparser_influx_output.add_argument('m', 'model', help='specify data type that will be storen in the database',
                                             default='PowerReport')
        subparser_influx_output.add_argument('n', 'name', help='specify puller name', default='pusher_influxdb')
        self.add_component_subparser('output', subparser_influx_output,
                                     help_str='specify a database input : --db_output database_name ARG1 ARG2 ... ')

        subparser_opentsdb_output = ComponentSubParser('opentsdb')
        subparser_opentsdb_output.add_argument('u', 'uri', help='sepcify openTSDB host')
        subparser_opentsdb_output.add_argument('p', 'port', help='specify openTSDB connection port', type=int)
        subparser_opentsdb_output.add_argument('metric_name', help='specify metric name')

        subparser_opentsdb_output.add_argument('m', 'model', help='specify data type that will be storen in the database',
                                             default='PowerReport')
        subparser_opentsdb_output.add_argument('n', 'name', help='specify puller name', default='pusher_opentsdb')
        self.add_component_subparser('output', subparser_opentsdb_output,
                                     help_str='specify a database input : --db_output database_name ARG1 ARG2 ... ')

    def parse_argv(self):
        try:
            return self.parse(sys.argv[1:])

        except BadValueException as exn:
            msg = 'CLI error : argument ' + exn.argument_name + ' : ' + exn.msg
            print(msg, file=sys.stderr)

        except MissingValueException as exn:
            msg = 'CLI error : argument ' + exn.argument_name + ' : expect a value'
            print(msg, file=sys.stderr)

        except BadTypeException as exn:
            msg = 'CLI error : argument ' + exn.argument_name + ' : expect '
            msg += exn.article + ' ' + exn.type_name
            print(msg, file=sys.stderr)

        except UnknowArgException as exn:
            msg = 'CLI error : unknow argument ' + exn.argument_name
            print(msg, file=sys.stderr)

        except BadContextException as exn:
            msg = 'CLI error : argument ' + exn.argument_name
            msg += ' not used in the correct context\nUse it with the following arguments :'
            for main_arg_name, context_name in exn.context_list:
                msg += '\n  --' + main_arg_name + ' ' + context_name
            print(msg, file=sys.stderr)

        sys.exit()


class Generator:

    def __init__(self, component_group_name):
        self.component_group_name = component_group_name

    def generate(self, config):
        if self.component_group_name not in config:
            print('CLI error : no ' + self.component_group_name + ' specified', file=sys.stderr)
            sys.exit()

        actors = {}

        for component_type, components_list in config[self.component_group_name].items():
            for component_name, component_config in components_list.items():
                try:
                    actors[component_name] = self._gen_actor(component_type, component_config, config)
                except KeyError as exn:
                    msg = 'CLI error : argument ' + exn.args[0]
                    msg += ' needed with --output ' + component_type
                    print(msg, file=sys.stderr)
                    sys.exit()

        return actors

    def _gen_actor(self, component_name, component_config, main_config):
        raise NotImplementedError()


class ModelNameAlreadyUsed(PowerAPIException):
    """
    Exception raised when attempting to add to a DBActorGenerator a model factory with a name already bound to another
    model factory in the DBActorGenerator
    """


class ModelNameAlreadyUsed(PowerAPIException):
    """
    Exception raised when attempting to add to a DBActorGenerator a database factory with a name already bound to another
    database factory in the DBActorGenerator
    """


class DBActorGenerator(Generator):

    def __init__(self, component_group_name):
        Generator.__init__(self, component_group_name)
        self.model_factory = {
            'HWPCReport': HWPCModel(),
            'PowerReport': PowerModel(),
            'FormulaReport': FormulaModel(),
            'ControlReport': ControlModel(),
        }

        self.db_factory = {
            'mongodb': lambda db_config: MongoDB(db_config['uri'], db_config['db'], db_config['collection']),
            'csv': lambda db_config: CsvDB(current_path=os.getcwd() if 'directory' not in db_config else db_config['directory'],
                                           files=[] if 'files' not in db_config else db_config['files']),
            'influxdb': lambda db_config: InfluxDB(db_config['uri'], db_config['port'], db_config['db']),
            'opentsdb': lambda db_config: OpenTSDB(db_config['uri'], db_config['port'], db_config['metric_name']),
        }

    def add_model_factory(self, model_name, model_factory):
        if model_name in self.model_factory:
            raise ModelNameAlreadyUsed()
        self.model_factory[model_name] = model_factory

    def add_db_factory(self, db_name, db_factory):
        if db_name in self.model_factory:
            raise ModelNameAlreadyUsed()
        self.model_factory[db_name] = db_factory

    def _generate_db(self, db_name, db_config, main_config):
        return self.db_factory[db_name](db_config)


    def _gen_actor(self, db_name, db_config, main_config):
        db = self._generate_db(db_name, db_config, main_config)
        model = self.model_factory[db_config['model']]
        name = db_config['name']
        return self._actor_factory(name, db, model, main_config['stream'], main_config['verbose'])

    def _actor_factory(self, name, db, model, stream_mode, level_logger):
        raise NotImplementedError()


class PullerGenerator(DBActorGenerator):

    def __init__(self, report_filter):
        DBActorGenerator.__init__(self, 'input')
        self.report_filter = report_filter

    def _actor_factory(self, name, db, model, stream_mode, level_logger):
        return PullerActor(name, db, self.report_filter, model, stream_mode, level_logger)


class PusherGenerator(DBActorGenerator):

    def __init__(self):
        DBActorGenerator.__init__(self, 'output')

    def _actor_factory(self, name, db, model, stream_mode, level_logger):
        return PusherActor(name, model, db, level_logger)

[((63, 8, 63, 33), 'powerapi.cli.parser.MainParser.__init__', 'MainParser.__init__', ({(63, 28, 63, 32): 'self'}, {}), '(self)', False, 'from powerapi.cli.parser import MainParser, ComponentSubParser\n'), ((69, 32, 69, 61), 'powerapi.cli.parser.ComponentSubParser', 'ComponentSubParser', ({(69, 51, 69, 60): '"""mongodb"""'}, {}), "('mongodb')", False, 'from powerapi.cli.parser import MainParser, ComponentSubParser\n'), ((79, 30, 79, 55), 'powerapi.cli.parser.ComponentSubParser', 'ComponentSubParser', ({(79, 49, 79, 54): '"""csv"""'}, {}), "('csv')", False, 'from powerapi.cli.parser import MainParser, ComponentSubParser\n'), ((90, 33, 90, 62), 'powerapi.cli.parser.ComponentSubParser', 'ComponentSubParser', ({(90, 52, 90, 61): '"""mongodb"""'}, {}), "('mongodb')", False, 'from powerapi.cli.parser import MainParser, ComponentSubParser\n'), ((101, 31, 101, 56), 'powerapi.cli.parser.ComponentSubParser', 'ComponentSubParser', ({(101, 50, 101, 55): '"""csv"""'}, {}), "('csv')", False, 'from powerapi.cli.parser import MainParser, ComponentSubParser\n'), ((110, 34, 110, 64), 'powerapi.cli.parser.ComponentSubParser', 'ComponentSubParser', ({(110, 53, 110, 63): '"""influxdb"""'}, {}), "('influxdb')", False, 'from powerapi.cli.parser import MainParser, ComponentSubParser\n'), ((120, 36, 120, 66), 'powerapi.cli.parser.ComponentSubParser', 'ComponentSubParser', ({(120, 55, 120, 65): '"""opentsdb"""'}, {}), "('opentsdb')", False, 'from powerapi.cli.parser import MainParser, ComponentSubParser\n'), ((159, 8, 159, 18), 'sys.exit', 'sys.exit', ({}, {}), '()', False, 'import sys\n'), ((254, 15, 254, 90), 'powerapi.puller.PullerActor', 'PullerActor', ({(254, 27, 254, 31): 'name', (254, 33, 254, 35): 'db', (254, 37, 254, 55): 'self.report_filter', (254, 57, 254, 62): 'model', (254, 64, 254, 75): 'stream_mode', (254, 77, 254, 89): 'level_logger'}, {}), '(name, db, self.report_filter, model, stream_mode, level_logger)', False, 'from powerapi.puller import PullerActor\n'), ((263, 15, 263, 57), 'powerapi.pusher.PusherActor', 'PusherActor', ({(263, 27, 263, 31): 'name', (263, 33, 263, 38): 'model', (263, 40, 263, 42): 'db', (263, 44, 263, 56): 'level_logger'}, {}), '(name, model, db, level_logger)', False, 'from powerapi.pusher import PusherActor\n'), ((170, 12, 170, 22), 'sys.exit', 'sys.exit', ({}, {}), '()', False, 'import sys\n'), ((209, 26, 209, 37), 'powerapi.report_model.HWPCModel', 'HWPCModel', ({}, {}), '()', False, 'from powerapi.report_model import HWPCModel, PowerModel, FormulaModel, ControlModel\n'), ((210, 27, 210, 39), 'powerapi.report_model.PowerModel', 'PowerModel', ({}, {}), '()', False, 'from powerapi.report_model import HWPCModel, PowerModel, FormulaModel, ControlModel\n'), ((211, 29, 211, 43), 'powerapi.report_model.FormulaModel', 'FormulaModel', ({}, {}), '()', False, 'from powerapi.report_model import HWPCModel, PowerModel, FormulaModel, ControlModel\n'), ((212, 29, 212, 43), 'powerapi.report_model.ControlModel', 'ControlModel', ({}, {}), '()', False, 'from powerapi.report_model import HWPCModel, PowerModel, FormulaModel, ControlModel\n'), ((52, 41, 52, 62), 'os.access', 'os.access', ({(52, 51, 52, 52): 'f', (52, 54, 52, 61): 'os.R_OK'}, {}), '(f, os.R_OK)', False, 'import os\n'), ((216, 41, 216, 108), 'powerapi.database.MongoDB', 'MongoDB', ({(216, 49, 216, 65): "db_config['uri']", (216, 67, 216, 82): "db_config['db']", (216, 84, 216, 107): "db_config['collection']"}, {}), "(db_config['uri'], db_config['db'], db_config['collection'])", False, 'from powerapi.database import MongoDB, CsvDB, InfluxDB, OpenTSDB\n'), ((219, 42, 219, 104), 'powerapi.database.InfluxDB', 'InfluxDB', ({(219, 51, 219, 67): "db_config['uri']", (219, 69, 219, 86): "db_config['port']", (219, 88, 219, 103): "db_config['db']"}, {}), "(db_config['uri'], db_config['port'], db_config['db'])", False, 'from powerapi.database import MongoDB, CsvDB, InfluxDB, OpenTSDB\n'), ((220, 42, 220, 113), 'powerapi.database.OpenTSDB', 'OpenTSDB', ({(220, 51, 220, 67): "db_config['uri']", (220, 69, 220, 86): "db_config['port']", (220, 88, 220, 112): "db_config['metric_name']"}, {}), "(db_config['uri'], db_config['port'], db_config['metric_name'])", False, 'from powerapi.database import MongoDB, CsvDB, InfluxDB, OpenTSDB\n'), ((182, 20, 182, 30), 'sys.exit', 'sys.exit', ({}, {}), '()', False, 'import sys\n'), ((217, 56, 217, 67), 'os.getcwd', 'os.getcwd', ({}, {}), '()', False, 'import os\n')]