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

# Copyright 2012-2013 Calculate Ltd. 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.

import os, pickle, threading
from os import path
import random
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:
    SuccessFinished = 0
    Worked = 1
    FailedFinished = 2
    NotFound = 3
    Paused = 4

class ProcessMode:
    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/sbin/cl-core"
    yield "/usr/sbin/cl-update"
    yield "/usr/sbin/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())

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 BaseException:
            pass
        with ignore(OSError):
            os.unlink(pidfile)


# process management
class CoreWsdl():
    # delete process id from list process
    def del_pid(self, pid):
        try:
            rst = []
            pid_str = str(pid)

            # open the file list of process
            with open(self.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(self.pids_file, 'w')
            fd.write('\n'.join(rst))
            fd.write('\n') # with join we lose the last newline char
            fd.close()
            self.glob_process_dict.pop(pid)
            self.glob_progress_dict.pop(pid)
            self.glob_table_dict.pop(pid)
            self.glob_frame_list.pop(pid)
            with ignore(OSError):
                rm_fn = path.join(self.pids, "%d.pid" % pid)
                if path.exists(rm_fn):
                    os.unlink(rm_fn)
            return 0
        except BaseException as e:
            return 1

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

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

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

    # issue new pid for created process
    def gen_pid(self):
        # flag = 1 - exists, 0 - missing in PID_FILE
        flag = 1
        # while exists generation new process id
        while flag == 1:
            new_pid = random.randint(1,self.max_pid)
            # find in file
            flag = self.find_pid_in_file(new_pid)
        # add new process id in list 
        self.add_pid_in_file(new_pid)
        return new_pid

    def check_sid_cert(self, sid):
        curThread = threading.currentThread()
        certificate = curThread.client_cert
        cert_id = find_cert_id(certificate, self.data_path, self.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(self.sids_file):
            return 0
#            temp = open(self.sids_file, 'w')
#            temp.close()
        fd = open(self.sids_file, 'r')
        while 1:
            try:
                # read all on one record
                list_sid = pickle.load(fd)
            except:
                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

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

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