###############################################################################
# File:        zx.py
# Appointment: common classes and functions
# Project:     zx
# Author:      AR
# Created:     25.09.2012
# Last change: 17.10.2016
###############################################################################
import csv
import ctypes
import os
import platform
import psutil
import threading


#>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
# Returns true if windows
#>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
def IsWindows():
    return platform.system() == "Windows"


#===============================================================================
# Represents area
#===============================================================================
class CArea:
    def __init__(self, aLeft = 0, aTop = 0, aWitdh = 0, aHeight = 0):
        self.Set(aLeft, aTop, aWitdh, aHeight)
        
    def Set(self, aLeft = 0, aTop = 0, aWitdh = 0, aHeight = 0):
        self.Left = aLeft
        self.Top = aTop
        self.Width = aWitdh
        self.Height = aHeight
        
    def SetByPoints(self, aLeft, aTop, aRight, aBottom):
        self.Set(aLeft, aTop, aRight - aLeft + 1, aBottom - aTop + 1)

    def GetLeft(self):
       return self.Left
    def GetTop(self):
        return self.Top
    def GetRight(self):
        return self.Left + self.Width
    def GetBottom(self):
        return self.Top + self.Height
    def GetWidth(self):
        return self.Width
    def GetHeight(self):
        return self.Height
#===============================================================================


#===============================================================================
#  Monitors pressed keys
#===============================================================================
class CKeysMonitor:
    def __init__(self):
        self.Clear()
      
    # Registeres a key (to be called in EVT_KEY_DOWN)
    def AddKey(self, key):
        self._keys.append(key)
        
    # Removes key ((to be called in EVT_KEY_DOWN)
    def RemoveKey(self, key):
        while key in self._keys:
            self._keys.remove(key)
            
    # Clearing all keys
    def Clear(self):
        self._keys = []
            
    # Checks if key registered. Returns True in success
    def FindKey(self, key):        
        return key in self._keys
#===============================================================================


#===============================================================================
# Wrapper arount API cfg functions api.zxGetCfgItem/api.zxSetCfgItem
#===============================================================================
class Config:
    #---------------------------------------------------------------------------
    # aAPI - api object
    def __init__(self, aAPI):
        self.API = aAPI
        self.API.zxGetCfgItem.restype = ctypes.c_char_p

    #---------------------------------------------------------------------------
    # Returns item by path
    def GetItem(self, aPath, aDefault = ""):
        item = self.API.zxGetCfgItem(aPath)
        return item if item != "" else aDefault
    # The same, but with section path defined and item name
    def GetSectionItem(self, aSectionPath, aItemName, aDefault = ""):
        return self.GetItem(aSectionPath + "." + aItemName, aDefault)

    #---------------------------------------------------------------------------
    # Sets item by path
    # Returns True in success
    def SetItem(self, aPath, aValue):
        return self.API.zxSetCfgItem(aPath, aValue)
    # The same, but with section path defined and item name
    def SetSectionItem(self, aSectionPath, aItemName, aValue):
        return self.SetItem(aSectionPath + "." + aItemName, aValue)
#===============================================================================


#===============================================================================
# Object with config
#===============================================================================
class CCfgHolder:
    #---------------------------------------------------------------------------
    # aCfg - config object
    # aCfgPath - section path where to save data
    # aCfgName - subsection name for the frame
    def __init__(self, aCfg, aCfgPath, aCfgName):
        self.__Cfg = aCfg
        self.__CfgSection = aCfgPath + "." + aCfgName

    #---------------------------------------------------------------------------
    # Returns cfg section
    def GetCfgSection(self):
        return self.__CfgSection
        
    #---------------------------------------------------------------------------
    # Returns a value by name from config
    def GetValue(self, aName, aDefault = ""):
        return self.__Cfg.GetSectionItem(self.__CfgSection, aName, aDefault)

    #---------------------------------------------------------------------------
    # Saves value
    def SetValue(self, aName, aValue):
        self.__Cfg.SetSectionItem(self.__CfgSection, aName, str(aValue))

#===============================================================================


#===============================================================================
# Unique id generator
#===============================================================================
class CSequence:
    # aStart - initial number
    # aDelta - step
    def __init__(self, aStart = 0, aDelta = 1):
        self.Current = aStart - aDelta
        self.__delta = aDelta
    # Returns next value starting from aStar
    def Next(self):
        self.Current += self.__delta
        return self.Current
#===============================================================================


#===============================================================================
# Smart delay
#===============================================================================
class CDelay:
    def __init__(self, aDelayTime = 0):
        self.__oneShotDelayTime = 0 # temporary delay, used to suspend the process one time
        self.__delayTime = aDelayTime
        self.__cancelEvent = threading.Event()

    # Returns delay time
    def GetDelayTime(self):
        return self.__delayTime

    # Returns true if cancelled
    def IsCancelled(self):
        return self.__cancelEvent.isSet()
        
    # Cancelling event
    def Cancel(self):
        self.__cancelEvent.set()
        
    # Temporary change delay time
    def SetOneShotDelay(self, aDelay):
        self.__oneShotDelayTime = aDelay

    def GetOneShotDelay(self):
        return self.__oneShotDelayTime
        
    # Suspends execution on delay time (if > 0)
    # Returns True if not cancelled
    def Suspend(self):
        if self.__delayTime > 0:
            delay = self.__oneShotDelayTime if self.__oneShotDelayTime > 0 else self.__delayTime
            self.__cancelEvent.wait(delay)
            if delay == self.__oneShotDelayTime:
                self.__oneShotDelayTime = 0 # reset one shot delay
        return self.__cancelEvent.isSet()
#-------------------------------------------------------------------------------

#>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
# Suspends process by aDelay if specified
# Returns true if delay has been cancelled. In this case further execution is to be interrupted
#>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
def Suspend(aDelay = None):
    return aDelay.Suspend() if aDelay is not None else False


        

#>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
# This function returns map from CSV
# aCSV - a string in a form "key:value;key1:value1..."
# aSep - separator between lines (in example above it would be ';')
# aPairSep - sepparator between key and value (in example above it would be ':')
#>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
def CSVToMap(aCSV, aSep, aPairSep):
    # Split on rows
    res = {}
    rows = aCSV.strip('"').split(aSep)
    for row in rows:
        pair = row.split(aPairSep)
        if len(pair) == 2:
            res[pair[0].strip('"')] = pair[1].strip('"')
            
    return res



#>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
# Return first item in sequence where aFunc(item) == True
# Returns None if not found
#>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
def Find(aSeq, aFunc):
    for i in aSeq:
      if aFunc(i): 
        return i
    return None



#>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
# Removes 1st element from a seq by aFunc
# Returns True if element has been removed
#>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
def Delete(aSeq, aFunc):
    for i in range(len(aSeq)):
      if aFunc(aSeq[i]):
        del aSeq[i]
        return True
    return False

    

#>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
# Helper - returns a sublist from aList for predicate
#>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
def GetSubListFor(aList, aFunc):
    if aFunc is None:
        return aList

    res = []
    for item in aList:
        if aFunc(item):
            res.append(item)
    return res


#>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
# Returns a number of running processes by name
#>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
def GetNumberOfRunningProcesses(aProcessName):
    count = 0
    for pid in psutil.pids():
        p = psutil.Process(pid)
        if p.name() == aProcessName:
            count += 1

    return count



