# -*- coding: utf-8 -*-

# Copyright 2012-2016 Mir Calculate. http://www.calculate-linux.org
#
#  Licensed under the Apache License, Version 2.0 (the "License");
#  you may not use this file except in compliance with the License.
#  You may obtain a copy of the License at
#
#      http://www.apache.org/licenses/LICENSE-2.0
#
#  Unless required by applicable law or agreed to in writing, software
#  distributed under the License is distributed on an "AS IS" BASIS,
#  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#  See the License for the specific language governing permissions and
#  limitations under the License.

from __future__ import print_function
from __future__ import absolute_import
import os
import sys

import calculate.contrib
from spyne import rpc
from spyne import Service
from spyne import String, Integer, Array

from calculate.core.server.core_interfaces import CoreServiceInterface
from calculate.lib.utils.files import readFile

_ = lambda x: x
from calculate.lib.cl_lang import getLazyLocalTranslate, setLocalTranslate

setLocalTranslate('cl_core3', sys.modules[__name__])

__ = getLazyLocalTranslate(_)

from .clean import sid_monitor, monitor
from .tasks import restart, dbus_stop
from .loaded_methods import LoadedMethods
from .api_types import Message, ReturnProgress, Table
from . import post_cert
from . import post_request
from . import send_cert

String.Attributes.encoding = "utf-8"

# class Basic(CoreServiceInterface):
class Basic(Service, CoreServiceInterface):
    """ Basic server class """
   
    SERV_VERS = 0.11
    glob_process_dict = {}
    process_pid = {}
    glob_progress_dict = {}
    glob_table_dict = {}
    glob_frame_list = {}
    manager = None

    data_path = None
    certbase = None
    rights = None
    sids = None
    pids = None
    sids_file = None
    ssl_certificate = None
    ssl_private_key = None
    cachedict = {}

    #workaround: Spyne won't let us use self ref in @rpc, 
    # and ctx leads to original service class (Basic in this case)
    # but some methods are gathered from CoreWsdl classes, so we need
    # a ref to combined class
    comb_class_ref = None

    # function getting object from cache
    @staticmethod
    def get_cache(sid, meth_name, obj_name):
        if sid in Basic.cachedict:
            if meth_name in Basic.cachedict[sid]:
                if obj_name in Basic.cachedict[sid][meth_name]:
                    return Basic.cachedict[sid][meth_name][obj_name]
        return None

    # function placing object in cache
    @staticmethod
    def set_cache(sid, meth_name, obj_name, obj, smart=True):
        try:
            if sid not in Basic.cachedict:
                Basic.cachedict[sid] = {}
            if meth_name not in Basic.cachedict[sid]:
                Basic.cachedict[sid][meth_name] = {}
            if obj_name not in Basic.cachedict[sid][meth_name]:
                Basic.cachedict[sid][meth_name][obj_name] = obj
                return True
            if smart:
                for var_name, var_value in obj.__dict__.viewitems():
                    if var_value is not None:
                        setattr(Basic.cachedict[sid][meth_name][obj_name],
                                var_name, var_value)
            else:
                Basic.cachedict[sid][meth_name][obj_name] = obj
            return True
        except Exception as e:
            if isinstance(e, SyntaxError):
                raise
            return False

   

    @staticmethod
    def clear_cache(sid, meth_name=None, obj_name=None):
        if sid not in Basic.cachedict:
            return True
        if meth_name:
            if meth_name not in Basic.cachedict[sid]:
                return True

            if obj_name:
                if obj_name not in Basic.cachedict[sid][meth_name]:
                    return True
                else:
                    obj = Basic.cachedict[sid][meth_name].pop(obj_name, None)
                    if hasattr(obj, 'close'):
                        obj.close()
            else:
                method_dict = Basic.cachedict[sid].pop(meth_name, None)
                for val_obj in method_dict.values():
                    if hasattr(val_obj, 'close'):
                        val_obj.close()
        else:
            session_dict = Basic.cachedict.pop(sid, None)
            for method_dict in session_dict.values():
                for val_obj in method_dict.values():
                    if hasattr(val_obj, 'close'):
                        val_obj.close()

    @staticmethod
    def clear_cache_method(method=None):
        for k, v in Basic.cachedict.items():
            Basic.clear_cache(k, method)

    @staticmethod
    def set_paths(data_path, certbase, serv_certbase, rights,
                  group_rights, sids, pids, sids_pids, sids_file, pids_file,
                  max_sid, max_pid, cert_path, log_filename,
                  cert="server.crt", key="server.key"):
        """ set system path for main class """
        Basic.data_path = data_path
        Basic.certbase = certbase
        Basic.serv_certbase = serv_certbase
        Basic.rights = rights
        Basic.group_rights = group_rights
        Basic.sids = sids
        Basic.pids = pids
        Basic.sids_pids = sids_pids
        Basic.sids_file = sids_file
        Basic.pids_file = pids_file
        Basic.ssl_certificate = cert
        Basic.ssl_private_key = key
        Basic.cert_path = cert_path
        Basic.max_sid = int(max_sid)
        Basic.max_pid = int(max_pid)
        Basic.log_filename = log_filename


    @staticmethod
    def set_comb_class_ref(comb_class_ref):
        Basic.comb_class_ref = comb_class_ref

    @staticmethod
    def run_tasks():
        """
        Запуситить регулярные задачи
        """
        import threading
        # start monitor and sid_monitor threads
        monitoring = threading.Thread(target=monitor,
                                      args=(Basic.certbase, Basic.sids_file))
        sid_mon = threading.Thread(target=sid_monitor,
                                   args=(Basic.sids_file, Basic.sids, Basic.comb_class_ref))
        restart_watchdog = threading.Thread(target=restart,
                                            args=(Basic.comb_class_ref.glob_process_dict,))
        dbus_stop_mon = threading.Thread(target=dbus_stop,
                                     args=(Basic.comb_class_ref.glob_process_dict, Basic.comb_class_ref.sids, Basic.comb_class_ref))
        threads = []
        for thread, success, failed in (
                (monitoring, _("General monitoring started"),
                 _("Monitoring error")),
                (sid_mon, _("Session monitoring started"),
                 _("Session monitoring failed")),
                (restart_watchdog, _("Restart watchdog started"),
                 _("Restart watchdog failed")),
                (dbus_stop_mon, _("Inactive watchdog started"),
                 _("Inactive watchdog failed"))):
            try:
                threads.append(thread)
                thread.daemon = True
                thread.start()
                print(success)
            except Exception:
                print(failed)
        print()

    @staticmethod
    def killall():
        sys.stdout.write('\n' + _('Closing all processes') + '...')
        sys.stdout.flush()
        import time
        # Waiting for closing
        for pid in Basic.process_pid.keys():
            try:
                os.kill(Basic.process_pid[pid].pid, 2)
            except OSError:
                pass
        while True:
            num_active_process = 0
            for pid in Basic.process_pid.keys():
                if Basic.process_pid[pid].is_alive():
                    num_active_process += 1

            if num_active_process:
                sys.stdout.write('.')
                sys.stdout.flush()
            else:
                print('\n' + _('All processes are closed.'))
                return 0
            time.sleep(0.5)
 

    @rpc(_returns=Array(Integer))
    def post_cert(ctx):
        # import post_cert
        returns = post_cert.serv_post_cert(Basic.comb_class_ref)
        return returns

    @rpc(Integer, _returns=Integer)
    def clear_session_cache(ctx, sid):
        check_sid = Basic.comb_class_ref.check_sid_cert(Basic.comb_class_ref, sid)
        if not check_sid:
            return 1
        # clear cache
        Basic.clear_cache(sid)
        return 0

    @rpc(Integer, String, _returns=Integer)
    def clear_method_cache(ctx, sid, method_name):
        check_sid = Basic.comb_class_ref.check_sid_cert(Basic.comb_class_ref, sid)
        if not check_sid:
            return 1
        # clear cache
        Basic.clear_cache(sid, method_name)
        return 0

    @rpc(Integer, Integer, _returns=Integer)
    def clear_pid_cache(ctx, sid, pid):
        if not Basic.comb_class_ref.check_sid_cert(Basic.comb_class_ref, sid):
            return 1

        if pid in Basic.comb_class_ref.find_sid_pid_file(Basic.comb_class_ref, sid):
            # clear pid cache
            Basic.comb_class_ref.delete_pid(Basic.comb_class_ref, sid, pid)
            return 0

        return 2

    @rpc(Integer, Integer, String, _returns=Array(Integer))
    def post_sid(self, sid, cert_id, lang):
        return Basic.comb_class_ref.sid_cmp(Basic.comb_class_ref, sid, cert_id, lang)

    @rpc(Integer, String, _returns=(Array(Integer), Array(Integer)))
    def init_session(ctx, sid, lang):
        return Basic.comb_class_ref.serv_init_session(Basic.comb_class_ref, sid, lang)

    @rpc(Integer, _returns=Array(String))
    # @Dec.check_permissions(['del_sid'])
    def del_sid(self, sid):
        flag = Basic.comb_class_ref.del_sid_pid(Basic.comb_class_ref, sid)
        Basic.comb_class_ref.clear_cache(int(sid))
        if not flag:
            return Basic.comb_class_ref.del_sid_from_file(Basic.comb_class_ref,
                 sid)
        else:
            return ['-1']

    @rpc(Integer, Integer, _returns=Integer)
    def pid_kill(ctx, pid, sid):
        return Basic.comb_class_ref.serv_pid_kill(Basic.comb_class_ref, pid, sid)

    @rpc(Integer, _returns=Array(Integer))
    def list_pid(ctx, sid):
        return Basic.comb_class_ref.find_sid_pid_file(Basic.comb_class_ref, sid)

    @rpc(Integer, String, _returns=Array(Array(String)))
    def get_methods(ctx, sid, client_type):
        return map(lambda x: map(str, x),
                   Basic.comb_class_ref.serv_get_methods(Basic.comb_class_ref, client_type))

    @rpc(Integer, _returns=Array(String))
    @LoadedMethods.core_method(rights=["get-sessions"])
    # @Dec.console('list-session')
    def get_sessions(ctx, sid):
        if not Basic.comb_class_ref.check_sid_cert(Basic.comb_class_ref, sid):
            return ['']
        return Basic.comb_class_ref.serv_get_sessions(Basic.comb_class_ref)

    @rpc(Integer, Integer, _returns=Array(String))
    # @Dec.check_permissions(["pid_info"])
    def pid_info(ctx, sid, pid):
        return Basic.comb_class_ref.serv_pid_info(Basic.comb_class_ref, sid, pid)

    @rpc(Integer, _returns=Array(String))
    @LoadedMethods.core_method(rights=["session_info"])
    def sid_info(ctx, sid):
        return Basic.comb_class_ref.serv_sid_info(Basic.comb_class_ref, sid)

    @rpc(Integer, String, _returns=Array(String))
    @LoadedMethods.check_permissions(["view_cert_right"])
    # @Dec.console('view-cert-right')
    # @Dec.gui('System')
    def view_cert_right(ctx, cert_id, client_type):
        return Basic.comb_class_ref.serv_view_cert_right(Basic.comb_class_ref,
        cert_id, Basic.data_path, client_type)

    @rpc(Integer, _returns=Integer)
    def active_client(ctx, sid):
        return Basic.comb_class_ref.active_clients(Basic.comb_class_ref, sid)

    @rpc(String, String, String, String, _returns=String)
    def post_client_request(ctx, request, ip, mac, client_type):
        res = post_request.serv_post_client_request(request, Basic.data_path,
                                                    ip, mac, client_type,
                                                    Basic.certbase,
                                                    Basic.cert_path)
        return res

    @rpc(String, String, _returns=Array(String))
    def get_client_cert(ctx, req_id, request):
        res = post_request.serv_get_client_cert(req_id, request,
                                                Basic.data_path, Basic.certbase,
                                                Basic.cert_path)
        return res

    @rpc(String, String, String, _returns=String)
    def post_server_request(ctx, request, ip, mac):
        res = post_request.serv_post_server_request(request, Basic.data_path,
                                                    ip, mac, Basic.serv_certbase,
                                                    Basic.cert_path)
        return res

    @rpc(String, String, _returns=Array(String))
    def get_server_cert(ctx, req_id, request):
        res = post_request.serv_get_server_request(req_id, request,
                                                   Basic.data_path,
                                                   Basic.serv_certbase,
                                                   Basic.cert_path)
        return res

    @rpc(_returns=String)
    def get_crl(self):
        if os.path.exists(Basic.data_path + '/server_certs/ca.crl'):
            return readFile(Basic.data_path + '/server_certs/ca.crl')
        return ' '

    @rpc(_returns=String)
    def get_server_host_name(ctx):
        import OpenSSL

        cert = readFile(Basic.ssl_certificate)
        cert_obj = OpenSSL.crypto.load_certificate(
            OpenSSL.SSL.FILETYPE_PEM, cert)
        subject = cert_obj.get_subject().get_components()
        for subj in subject:
            if subj[0] == 'CN':
                return subj[1]
        return ''

    @rpc(_returns=String)
    def get_ca(ctx):
        return send_cert.get_ca(Basic.cert_path)

    #moved here from api_types:

    @rpc(Integer, Integer, String, _returns=Array(Message))
    # @Dec.check_permissions()
    def get_frame(ctx, sid, pid, client_type):
        return Basic.comb_class_ref.client_get_frame(Basic.comb_class_ref, sid, pid, client_type)

    @rpc(Integer, Integer, _returns=Array(Message))
    def get_entire_frame(self, sid, pid):
        return Basic.comb_class_ref.client_get_entire_frame(Basic.comb_class_ref, sid, pid)

    @rpc(Integer, Integer, Integer, _returns=ReturnProgress)
    # @Dec.check_permissions()
    def get_progress(self, sid, pid, id):
        return Basic.comb_class_ref.client_get_progress(Basic.comb_class_ref, sid, pid, id)

    @rpc(Integer, Integer, Integer, _returns=Table)
    # @Dec.check_permissions()
    def get_table(self, sid, pid, id):
        return Basic.comb_class_ref.client_get_table(Basic.comb_class_ref, sid, pid, id)

    # get messages, transferred from client
    @rpc(Integer, Integer, String, _returns=Message)
    # @Dec.check_permissions()
    def send_message(self, sid, pid, text):
        return Basic.comb_class_ref.client_send_message(Basic.comb_class_ref, sid, pid, text)
