# -*- 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 pickle
import threading
from os import path
import random
from calculate.core.server.core_interfaces import CoreServiceInterface
from calculate.lib.utils.files import listDirectory, readFile, readLinesFile
from calculate.lib.utils.tools import ignore
import socket

from .cert_cmd import find_cert_id


class ProcessStatus(object):
    SuccessFinished = 0
    Worked = 1
    FailedFinished = 2
    NotFound = 3
    Paused = 4


class ProcessMode(object):
    CoreDaemon = "core"
    LocalCall = "local"


def get_symlink_commands():
    """
    Получить список команд утилит
    """
    symlinks = "/var/lib/calculate/calculate-core/conf/symlinks"
    for line in readLinesFile(symlinks):
        yield line.strip()
    yield "/usr/bin/cl-core"
    yield "/usr/bin/cl-update"
    yield "/usr/bin/cl-core-patch"


def search_worked_process(method_name, clVars,
                          statuses=(ProcessStatus.Worked,)):
    """
    Найти все работающие процессы

    Возвращает список процессов со статусом Worked и существующем системным
    процессом
    """

    def generator():
        pids = clVars.Get('core.cl_core_pids_path')
        for pidfile in listDirectory(pids, fullPath=True):
            try:
                status = pickle.load(open(pidfile))
                if ((method_name is None or status['name'] == method_name) and
                            status['status'] in statuses):
                    pid_path = path.join("/proc", str(status['os_pid']))
                    if path.exists(pid_path):
                        cmdline = readFile(path.join(pid_path, "cmdline"))
                        if cmdline and any(x in cmdline
                                           for x in get_symlink_commands()):
                            yield status['os_pid']
            except (socket.error, ValueError, KeyError, EOFError, OSError):
                pass

    return list(generator())


worked_filter = lambda x: x['status'] == ProcessStatus.Worked


def search_worked_process2(clVars, filter_func=lambda x: True,
                           status_filter=worked_filter):
    pids = clVars.Get('core.cl_core_pids_path')
    for pidfile in listDirectory(pids, fullPath=True):
        try:
            status = pickle.load(open(pidfile))
            if status_filter(status) and filter_func(status):
                pid_path = path.join("/proc", str(status['os_pid']))
                if path.exists(pid_path):
                    cmdline = readFile(path.join(pid_path, "cmdline"))
                    if cmdline and any(x in cmdline
                                       for x in get_symlink_commands()):
                        yield status['os_pid']
        except (socket.error, ValueError, KeyError, EOFError, OSError):
            pass


def get_pid_info(clVars, statuses=(ProcessStatus.Worked,)):
    """
    Получить информацию о процессах
    """

    def generator():
        pids = clVars.Get('core.cl_core_pids_path')
        for pidfile in listDirectory(pids, fullPath=True):
            try:
                status = pickle.load(open(pidfile))
                if status['status'] in statuses:
                    if path.exists(path.join("/proc", str(status['os_pid']))):
                        yield status
            except (socket.error, ValueError, KeyError, EOFError, OSError):
                pass

    return list(generator())

    # try:
    #    pidfile = path.join(pids,str(pid))
    #    status = pickle.load(open(pidfile))
    #    return status
    # except (socket.error, ValueError, IOError, KeyError, EOFError, OSError):
    #    return None


def clear_finished_pids(clVars):
    """
    Удалить все идентификационные файлы завершившихся процессов
    """
    pids = clVars.Get('core.cl_core_pids_path')
    for pidfile in listDirectory(pids, fullPath=True):
        try:
            d = pickle.load(open(pidfile))
            if path.exists(path.join("/proc", str(d['os_pid']))):
                continue
        except Exception:
            pass
        with ignore(OSError):
            os.unlink(pidfile)


# process management
class CoreWsdl(CoreServiceInterface):
    #for debugging:
    gen_pid_testing_val = "Gen pid represents"

    # delete process id from list process
    @staticmethod
    def del_pid(cls, pid):
        try:
            rst = []
            pid_str = str(pid)

            # open the file list of process
            with open(cls.pids_file) as fd:
                t = fd.read()
                for line in t.splitlines():
                    # Leave all but removed
                    if line != pid_str:
                        rst.append(line)

            # write all in file
            fd = open(cls.pids_file, 'w')
            fd.write('\n'.join(rst))
            fd.write('\n')  # with join we lose the last newline char
            fd.close()
            cls.glob_process_dict.pop(pid)
            cls.glob_progress_dict.pop(pid)
            cls.glob_table_dict.pop(pid)
            cls.glob_frame_list.pop(pid)
            with ignore(OSError):
                rm_fn = path.join(cls.pids, "%d.pid" % pid)
                if path.exists(rm_fn):
                    os.unlink(rm_fn)
            return 0
        except Exception:
            return 1

    # find process id in file processes, 1 - yes, 0 - none
    @staticmethod
    def find_pid_in_file(cls, find_pid):
        temp_line = ''

        # create, if file not exists
        if not os.path.exists(cls.pids_file):
            temp = open(cls.pids_file, 'w')
            temp.close()
        with open(cls.pids_file) as fd:
            t = fd.read()
            # for each line
            for line in t.splitlines():
                try:
                    temp_line = int(line)
                except ValueError:
                    pass
                # if process id found
                if temp_line == find_pid:
                    return 1
        fd.close()
        return 0

    # add process id in file
    @staticmethod
    def add_pid_in_file(cls, pid):
        pid_t = str(pid)
        fd = open(cls.pids_file, 'a')
        fd.write(pid_t)
        fd.write('\n')
        fd.close()
        return 0

    # issue new pid for created process
    @staticmethod
    def gen_pid(cls):
        while True:
            new_pid = random.randint(1, cls.max_pid)
            # flag = 1 - exists, 0 - missing in PID_FILE
            if cls.find_pid_in_file(cls, new_pid) == 0:
                cls.add_pid_in_file(cls, new_pid)
                return new_pid

    @staticmethod
    def check_sid_cert(cls, sid):
        curThread = threading.currentThread()
        certificate = curThread.client_cert
        cert_id = find_cert_id(certificate, cls.data_path, cls.certbase)

        # if certificate not found in database
        if cert_id == 0:
            return -1
        # check, This certificate is launched session
        # Data taken from sid.db
        flag = 0
        # create, if file not exists
        if not os.path.exists(cls.sids_file):
            return 0
        # temp = open(cls.sids_file, 'w')
        #            temp.close()
        fd = open(cls.sids_file, 'r')
        while 1:
            try:
                # read all on one record
                list_sid = pickle.load(fd)
            except (EOFError, KeyError, IOError):
                break
            # when session id equal readable...
            if int(sid) == int(list_sid[0]):
                # ... and certificate id equal launched this session...
                if int(cert_id) == int(list_sid[1]):
                    # ... set flag
                    flag = 1
        fd.close()
        # if validation fails
        return flag

    @staticmethod
    def serv_pid_kill(cls, pid, sid):
        """ Set flag to complete the process """
        check_sid = cls.check_sid_cert(cls, sid)
        if not check_sid:
            return -2

        # write complete flag (pid_list[6] = 1) in process file
        if not os.path.exists(cls.pids):
            os.mkdir(cls.pids)
        if pid not in cls.process_pid:
            return 3
        meth = cls.process_pid[pid]
        if meth.is_alive():
            try:
                os.kill(meth.pid, 2)
            except OSError as e:
                print('No such process %d' % meth.pid, e)
                return 1
        return 0
