# Copyright (C) 2010,2011  Internet Systems Consortium.
#
# Permission to use, copy, modify, and distribute this software for any
# purpose with or without fee is hereby granted, provided that the above
# copyright notice and this permission notice appear in all copies.
#
# THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SYSTEMS CONSORTIUM
# DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
# INTERNET SYSTEMS CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT,
# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING
# FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
# WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.

"""
A mock-up module of isc.cc.session

*** NOTE ***
It is only for testing stats_httpd module and not reusable for
external module.
"""

import json
import os
from isc.cc.session import Session

COMMAND_CONFIG_UPDATE = "config_update"

def parse_answer(msg):
    assert 'result' in msg
    try:
        return msg['result'][0], msg['result'][1]
    except IndexError:
        return msg['result'][0], None

def create_answer(rcode, arg = None):
    if arg is None:
        return { 'result': [ rcode ] }
    else:
        return { 'result': [ rcode, arg ] }

def parse_command(msg):
    assert 'command' in msg
    try:
        return msg['command'][0], msg['command'][1]
    except IndexError:
        return msg['command'][0], None

def create_command(command_name, params = None):
    if params is None:
        return {"command": [command_name]}
    else:
        return {"command": [command_name, params]}

def module_spec_from_file(spec_file, check = True):
    try:
        file = open(spec_file)
        json_str = file.read()
        module_spec = json.loads(json_str)
        file.close()
        return ModuleSpec(module_spec['module_spec'], check)
    except IOError as ioe:
        raise ModuleSpecError("JSON read error: " + str(ioe))
    except ValueError as ve:
        raise ModuleSpecError("JSON parse error: " + str(ve))
    except KeyError as err:
        raise ModuleSpecError("Data definition has no module_spec element")

class ModuleSpecError(Exception):
    pass

class ModuleSpec:
    def __init__(self, module_spec, check = True):
        self._module_spec = module_spec

    def get_config_spec(self):
        return self._module_spec['config_data']

    def get_commands_spec(self):
        return self._module_spec['commands']

    def get_module_name(self):
        return self._module_spec['module_name']

class ModuleCCSessionError(Exception):
    pass

class DataNotFoundError(Exception):
    pass

class ConfigData:
    def __init__(self, specification):
        self.specification = specification

    def get_value(self, identifier):
        """Returns a tuple where the first item is the value at the
           given identifier, and the second item is absolutely False
           even if the value is an unset default or not. Raises an
           DataNotFoundError if the identifier is not found in the
           specification file.
           *** NOTE ***
           There are some differences from the original method. This
           method never handles local settings like the original
           method. But these different behaviors aren't so big issues
           for a mock-up method of stats_httpd because stats_httpd
           calls this method at only first."""
        for config_map in self.get_module_spec().get_config_spec():
            if config_map['item_name'] == identifier:
                if 'item_default' in config_map:
                    return config_map['item_default'], False
        raise DataNotFoundError("item_name %s is not found in the specfile" % identifier)

    def get_module_spec(self):
        return self.specification

class ModuleCCSession(ConfigData):
    def __init__(self, spec_file_name, config_handler, command_handler, cc_session = None):
        module_spec = module_spec_from_file(spec_file_name)
        ConfigData.__init__(self, module_spec)
        self._module_name = module_spec.get_module_name()
        self.set_config_handler(config_handler)
        self.set_command_handler(command_handler)
        if not cc_session:
            self._session = Session(verbose=True)
        else:
            self._session = cc_session

    def start(self):
        pass

    def close(self):
        self._session.close()

    def check_command(self, nonblock=True):
        msg, env = self._session.group_recvmsg(nonblock)
        if not msg or 'result' in msg:
            return
        cmd, arg = parse_command(msg)
        answer = None
        if cmd == COMMAND_CONFIG_UPDATE and self._config_handler:
            answer = self._config_handler(arg)
        elif env['group'] == self._module_name and self._command_handler:
            answer = self._command_handler(cmd, arg)
        if answer:
            self._session.group_reply(env, answer)

    def set_config_handler(self, config_handler):
        self._config_handler = config_handler
        # should we run this right now since we've changed the handler?

    def set_command_handler(self, command_handler):
        self._command_handler = command_handler

    def get_module_spec(self):
        return self.specification

    def get_socket(self):
        return self._session._socket

