import threading, platform
import whatap.agent.secure.security_master as secure
import whatap.agent.data.data_pack as data
from whatap.agent.counter.task_extrainfo import TaskExtraINFO
from whatap.agent.counter.task_base import TaskBase
from whatap.agent.counter.task_disk import TaskDisk
from whatap.agent.counter.task_network import TaskNet
from whatap.agent.counter.task_proc import TaskProc
from whatap.agent.counter.task_log import TaskLogEvent
from whatap.agent.counter.task_config import TaskConfig
from whatap.agent.counter.task_filesystem import TaskFileSystem
from whatap.agent.counter.task_tcp import TaskTCP
from whatap.agent.counter.task_hostinfo import TaskHostInfo
from whatap.agent.counter.task_infra_cpu import TaskInfraCPU
from whatap.agent.counter.task_infra_mem import TaskInfraMem
from whatap.agent.counter.task_infra_kernel import TaskInfraKernel
from whatap.agent.counter.task_infra_swap import TaskInfraSwap
from whatap.agent.counter.task_infra_disk import TaskInfraDisk
from whatap.agent.counter.task_infra_network import TaskInfraNetwork
from whatap.agent.counter.task_infra_process import TaskInfraProcess
from whatap.agent.counter.task_infra_process_topn_group import TaskInfraProcessTopNAndGroup
from whatap.agent.counter.task_infra_nic_perf import TaskInfraNicPerf
from whatap.agent.counter.task_infra_disk_perf import TaskInfraDiskPerf
from whatap.agent.counter.task_infra_filesystem import TaskInfraFileSystem
from whatap.agent.counter.task_infra_cpu_core import TaskInfraCPUCore
from whatap.agent.counter.task_infra_zfs_perf import TaskInfraZFSPerf
from whatap.agent.counter.task_infra_zpool_perf import TaskInfraZpoolPerf
from whatap.agent.counter.task_infra_netstat import TaskInfraNetstat
from whatap.agent.counter.task_infra_ntp_info import TaskInfraNtpInfo
from whatap.agent.counter.task_infra_nic_info import TaskInfraNicInfo

from whatap.util.date_util import DateUtil as dateutil
import whatap.util.logging_util as logging_util
import whatap.util.panic_util as panic_util
import whatap.osinfo as osinfo
import whatap.agent.conf.configure as config
import whatap.pack.smbase_pack as smbase_pack
import time
from datetime import datetime, timedelta
import multiprocessing

module_loaded = datetime.now()
last_task_timestamp = 0
last_task_name = ""
hangTask = None

AGENT_VERSION = "unknown"  # injected by whatap_infrad at startup (see whatap_infrad:main)

class TimeoutException(Exception):
    pass

class TaskAction(threading.Thread):
    def __init__(self, task, name, lastActTime, timeout, timeoutException):
        threading.Thread.__init__(self)
        self.p = None
        self.os = None
        self.pcode = None
        self.oid = None
        self.now = None
        self.timeout = float(timeout)
        self.done_event = threading.Event()
        self.done_condition = threading.Condition()
        self.hangCheck = False
        self.hangCount = 0

        self.task  = task
        self.name = name
        self.lastActTime = lastActTime

        self.timeoutException = timeoutException
        if timeoutException:
            self.setDaemon(True)

    def set(self, os, pcode, oid, now):
        self.os = os
        self.pcode = pcode
        self.oid = oid
        self.now = now

    def run(self):
        self.done_event.set()
        while True:
            with self.done_condition:
                if self.done_event.is_set():
                    self.done_condition.wait()

            if not self.done_event.is_set():
                try:
                    self.p = self.task.process(self.os, self.pcode, self.oid, self.now)
                except Exception, e:
                    self.p = None
                    logging_util.errorStack(e)

                self.done_event.set()

    def get(self):
        if not self.timeoutException:
            return self.task.process(self.os, self.pcode, self.oid, self.now)

        if self.hangCheck:
            if not self.done_event.is_set():
                self.hangCount += 1
                raise TimeoutException("Hang Exception")
            
        with self.done_condition:
            self.done_event.clear()
            self.done_condition.notify()

        self.done_event.wait(timeout=self.timeout)

        if not self.done_event.is_set():
            self.hangCheck = True
            self.hangCount = 1
            raise TimeoutException("Timeout Exception")
        else:
            self.hangCheck = False
            self.hangCount = 0
            p = self.p
            self.p = None
            return p
        
        return None



class __CounterManager(threading.Thread):
    def __init__(self):
        threading.Thread.__init__(self, name='__CounterManager')
        self.setDaemon(True)

    def run(self):
        global last_task_timestamp
        global hangTask

        tasks = []
        securetasks = []
        packListTasks = []

        secu = secure.GetSecurityMaster()
        os = 0
        system = platform.system().lower()
        if system == "windows":
            os = smbase_pack.OS_WINDOW
        elif system == "linux":
            os = smbase_pack.OS_LINUX
        elif system == "darwin":
            os = smbase_pack.OS_OSX
        elif system == "sunos":
            os = smbase_pack.OS_SUNOS
        elif system == "aix":
            os = smbase_pack.OS_AIX
        elif system == "hp-ux":
            os = smbase_pack.OS_HPUX
        else:
            return

        taskTimeoutExcept = config.GetConfig().task_timeout_except
        taskTimeout = config.GetConfig().task_timeout
        taskHealthCheckCount = config.GetConfig().task_health_check_count

        infraOption = config.GetConfig().CategoryV2Enabled
        serverOption = config.GetConfig().CategoryV1Enabled

        #init
        logging_util.info("[CounterManager] Init Start")
        osinfo.GetServerInfoV2()
        if config.GetConfig().task_disk and serverOption:
            osinfo.GetDisk()
        logging_util.info("[CounterManager] Init End")

        if serverOption:
            if config.GetConfig().task_base:
                base = TaskAction(TaskBase(), "Base", 0, taskTimeout, taskTimeoutExcept)
                tasks.append(base)

            # with infra fs
            #Since the data sets are similar, it was transferred to infra fs
            #if config.GetConfig().task_disk:
            #    disk = TaskAction(TaskDisk(), "Disk", 0, taskTimeout, taskTimeoutExcept)
            #    tasks.append(disk)
            #    if taskTimeoutExcept and taskTimeout != 0:
            #        disk.start()
            
            #with infra net
            #if config.GetConfig().task_net:
            #    net = TaskAction(TaskNet(), "Net", 0, taskTimeout, taskTimeoutExcept)
            #    tasks.append(net)

            if config.GetConfig().task_proc:
                proc = TaskAction(TaskProc(), "Proc", 0, taskTimeout, taskTimeoutExcept)
                tasks.append(proc)
            
            if config.GetConfig().task_config:
                con = TaskAction(TaskConfig(), "Config", 0, taskTimeout, taskTimeoutExcept)
                tasks.append(con)

            if config.GetConfig().task_filesystem:
                filesystem = TaskAction(TaskFileSystem(), "Filesystem", 0, taskTimeout, taskTimeoutExcept)
                tasks.append(filesystem)

            if config.GetConfig().task_tcpcheck:
                tcpcheck = TaskAction(TaskTCP(), "TCPCheck", 0, taskTimeout, taskTimeoutExcept)
                tasks.append(tcpcheck)

            if config.GetConfig().task_logevent:
                logevent = TaskAction(TaskLogEvent(), "LogEvent", 0, taskTimeout, taskTimeoutExcept)
                securetasks.append(logevent)

            if config.GetConfig().task_osinfo:
                info = TaskAction(TaskExtraINFO(agent_version = AGENT_VERSION), "OSInfo", 0, taskTimeout, taskTimeoutExcept)
                securetasks.append(info)


        if config.GetConfig().HostInfoV2Enabled or infraOption:
            hostinfo = TaskAction(TaskHostInfo(agent_version = AGENT_VERSION), "HostInfo", 0, taskTimeout, taskTimeoutExcept)
            securetasks.append(hostinfo)

        unfoldingInterval = int(config.GetConfig().CategoryV2Interval)
        foldingInterval = int(config.GetConfig().CategoryV2FoldingInterval)

        #with server_disk
        infraFileSystem = TaskAction(TaskInfraFileSystem(interval = foldingInterval, infra=infraOption, server=(config.GetConfig().task_disk and serverOption)), "InfraFileSystemWithDisk", 0, taskTimeout, taskTimeoutExcept)
        packListTasks.append(infraFileSystem)
            
        #with server_network
        infraNicPerf = TaskAction(TaskInfraNicPerf(interval = foldingInterval, infra = infraOption, server=(config.GetConfig().task_net and serverOption)), "InfraNicPerf", 0, taskTimeout, taskTimeoutExcept)
        packListTasks.append(infraNicPerf)

        if infraOption:
            infraCpu = TaskAction(TaskInfraCPU(unfoldingInterval), "InfraCPU", 0, taskTimeout, taskTimeoutExcept)
            tasks.append(infraCpu)

            infraMem = TaskAction(TaskInfraMem(unfoldingInterval), "InfraMem", 0, taskTimeout, taskTimeoutExcept)
            tasks.append(infraMem)

            infraKernel = TaskAction(TaskInfraKernel(unfoldingInterval), "InfraKernel", 0, taskTimeout, taskTimeoutExcept)
            tasks.append(infraKernel)

            infraSwap = TaskAction(TaskInfraSwap(unfoldingInterval), "InfraSwap", 0, taskTimeoutExcept, taskTimeoutExcept)
            tasks.append(infraSwap)

            if config.GetConfig().DiskIOEnabled:
                infraDisk = TaskAction(TaskInfraDisk(unfoldingInterval), "InfraDisk", 0, taskTimeout, taskTimeoutExcept)
                tasks.append(infraDisk)

            infraNetwork = TaskAction(TaskInfraNetwork(unfoldingInterval), "InfraNetwork", 0, taskTimeout, taskTimeoutExcept)
            tasks.append(infraNetwork)

            infraProcess= TaskAction(TaskInfraProcess(unfoldingInterval), "InfraProcess", 0, taskTimeout, taskTimeoutExcept)
            tasks.append(infraProcess)

            infraProcessTopNAndGroup = TaskAction(TaskInfraProcessTopNAndGroup(interval = foldingInterval), "InfraProcessTopNAndGroup", 0, taskTimeout, taskTimeoutExcept)
            packListTasks.append(infraProcessTopNAndGroup)


            if config.GetConfig().DiskIOEnabled:
                infraDiskPerf = TaskAction(TaskInfraDiskPerf(foldingInterval), "InfraDiskPerf", 0, taskTimeout, taskTimeoutExcept)
                tasks.append(infraDiskPerf)

            infraCpuCore = TaskAction(TaskInfraCPUCore(foldingInterval), "InfraCpuCore", 0, taskTimeout, taskTimeoutExcept)
            tasks.append(infraCpuCore)

            infraZfsInfo = TaskAction(TaskInfraZFSPerf(foldingInterval), "InfraZFSInfo", 0, taskTimeout, taskTimeoutExcept)
            tasks.append(infraZfsInfo)

            infraZpoolPerf = TaskAction(TaskInfraZpoolPerf(foldingInterval), "InfraZpoolPerf", 0, taskTimeout, taskTimeoutExcept)
            tasks.append(infraZpoolPerf)

            infraNetstat = TaskAction(TaskInfraNetstat(60), "InfraNetstat", 0, taskTimeout, taskTimeoutExcept)
            tasks.append(infraNetstat)

            infraNtpInfo = TaskAction(TaskInfraNtpInfo(60), "InfraNtpInfo", 0, taskTimeout, taskTimeoutExcept)
            tasks.append(infraNtpInfo)

            infraNicInfo = TaskAction(TaskInfraNicInfo(60), "InfraNicInfo", 0, taskTimeout, taskTimeoutExcept)
            packListTasks.append(infraNicInfo)

        #thread mode
        if taskTimeoutExcept and  taskTimeout != 0:
            for each_task in tasks:
                each_task.start()

            for each_task in packListTasks:
                each_task.start()
                
            for each_task in securetasks:
                each_task.start()

        while True:
            try:
                time.sleep(0.5)
                now = osinfo.GetUptime()
                now_timestamp = dateutil.now()
                for each_task in tasks:
                    if each_task.task.interval() >= 2 and now >= each_task.lastActTime+each_task.task.interval() :
                        each_task.lastActTime = now
                        try:
                            logging_util.debug("[CounterManager] TaskStart : ", each_task.name)
                            last_task_name = each_task.name
                            
                            each_task.set(os, secu.pcode, secu.oid, now_timestamp)
                            p = each_task.get()

                            if p :
                                logging_util.debug("[CounterManager] TaskSuccess : ", each_task.name)
                                data.SendHide(p)
                                last_task_timestamp = datetime.now()
                        except TimeoutException, e:
                            if each_task.hangCount >= taskHealthCheckCount:
                                hangTask = each_task.name
                            message = "[CounterManager] Task : {}, Status : {}".format(each_task.name, e)
                            logging_util.error(message)
                            panic_util.AgentErrorLogEventAsync(message)
                        except Exception, e:
                            logging_util.logStack(e)

                for each_task in packListTasks:
                    if each_task.task.interval() >= 5 and now >= each_task.lastActTime + each_task.task.interval():
                        each_task.lastActTime = now
                        try:
                            logging_util.debug("[CounterManager] TaskStart : ", each_task.name)
                            last_task_name = each_task.name
                            
                            each_task.set(os, secu.pcode, secu.oid, now_timestamp)
                            ps = each_task.get()

                            if ps :
                                logging_util.debug("[CounterManager] TaskSuccess : ", each_task.name)
                                for p in ps:
                                    if p:
                                        data.SendHide(p)
                                last_task_timestamp = datetime.now()

                        except TimeoutException, e:
                            if each_task.hangCount >= taskHealthCheckCount:
                                hangTask = each_task.name
                            message = "[CounterManager] Task : {}, Status : {}".format(each_task.name, e)
                            logging_util.error(message)
                            panic_util.AgentErrorLogEventAsync(message)
                        except Exception, e:
                            logging_util.logStack(e)


                for each_task in securetasks:
                    if each_task.task.interval() >= 5 and now >= each_task.lastActTime + each_task.task.interval():
                        each_task.lastActTime = now
                        try:
                            logging_util.debug("[CounterManager] TaskStart : ", each_task.name)
                            last_task_name = each_task.name

                            each_task.set(os, secu.pcode, secu.oid, now_timestamp)
                            p = each_task.get()

                            if p :
                                logging_util.debug("[CounterManager] TaskSuccess : ", each_task.name)
                                data.SendSecure(p)
                                last_task_timestamp = datetime.now()
                        except TimeoutException, e:
                            if each_task.hangCount >= taskHealthCheckCount:
                                hangTask = each_task.name
                            message = "[CounterManager] Task : {}, Status : {}".format(each_task.name, e)
                            logging_util.error(message)
                            panic_util.AgentErrorLogEventAsync(message)
                        except Exception, e:
                            logging_util.logStack(e)
            except Exception, e:
                logging_util.logStack(e)

            
counterManager =__CounterManager()

def startCounterManager():
    counterManager.start()

def isOK():
    global last_task_timestamp
    global hangTask
    ret = True
    now = datetime.now()
    if now-module_loaded > timedelta(minutes = config.GetConfig().HealthCheckCoolTime):
        if last_task_timestamp and now-last_task_timestamp > timedelta(minutes = 1):
            ret = ret and False
            message = "[CounterManager] Health Check Fail Timeout - Last Task : {}".format(last_task_name)
            logging_util.error(message)
            panic_util.AgentErrorLogEventAsync(message)
        if hangTask:
            ret = ret and False
            message = "[CounterManager] Health Check Fail Task Hang : {}".format(hangTask)
            logging_util.error(message)
            panic_util.AgentErrorLogEventAsync(message)

    return ret
