#!/usr/bin/env python @ZL
# coding: utf-8

import platform, socket, re, uuid, json, psutil, logging, sys, ctypes, pyautogui
import pathlib, datetime, os, glob, time
import pandas as pd
import win32com.client as win32

class CIE_Converter:
    """A module to convert CIE spaces(CIE1976 <-> CIE1931)  
    Author: @ZL, 20200331

    It provides the following operations.
    - convert CIE1976 space coordiates(u', v') to CIE1931 space coorinates(x, y)
    - convert CIE1931 space coorinates(x, y) to CIE1976 space coordiates(u', v')

    Refer to the standards of CIE spaces on internet
    """
    def __init__(self, a: float, b: float, color_space: str):
        self.a = a
        self.b = b
        self.color_space = color_space

    def __repr__(self):
        if self.color_space == 'xy':
            return "xy->u\'v\', u\':{:.4f}, v\':{:.4f}".format(*self.converter())
        if self.color_space == 'dudv':
            return "u\'v\'->xy, x :{:.4f}, y: {:.4f}".format(*self.converter())
        else:
            return 'incorrect color space'

    def converter(self):
        def convert_dudv_to_xy(du: float, dv: float):
            """CIE1976 space(du,dv) to CIE1931 space(x,y) @ZL"""
            y = (3*dv)/(9*du/2 - 12*dv + 9)
            x = (du/dv)*9/4*y
            return x, y

        def convert_xy_to_dudv(x: float, y: float):
            """CIE1931 space(x,y) to CIE1976 space(du,dv) @ZL"""
            du = (4*x)/(12*y - 2*x + 3)
            dv = (9*y)/(12*y - 2*x + 3)
            return du, dv

        if self.color_space == 'xy':
            return convert_xy_to_dudv(self.a, self.b)
        if self.color_space == 'dudv':
            return convert_dudv_to_xy(self.a, self.b)
        else:
            return

class ReportMail:
    """
    this module is specialization for sending PowerPoint Report via email

    it contains the following functionalities:
    - control Outlook Application
    - construct email body in HTML format
    - send email
    - quit Outlook Application

    about
    - author: @ZL, 20201223

    """
    def __init__(self, ppt_name: str, dst_dir: str, slide_img_folder: str, src_ppt_file: str):
        self.__outlook          = win32.Dispatch('outlook.application')
        self.__mail             = self.__outlook.CreateItem(0)
        self.ppt_name           = ppt_name
        self.dst_dir            = dst_dir
        self.slide_img_folder   = slide_img_folder
        self.src_ppt_file       = src_ppt_file

    def attachments(self):
        img_id: int         = 1
        img_body_slide: str = ""
        for file in sorted(pathlib.Path(self.slide_img_folder).glob("*.png")):
            ##<~ attachment
            fp: str    = str(file.absolute())
            attachment = self.__mail.Attachments.Add(fp) # abs path only
            ## how: [https://stackoverflow.com/questions/44544369/i-am-not-able-to-add-an-image-in-email-body-using-python-i-am-able-to-add-a-pi]
            attachment.PropertyAccessor.SetProperty("http://schemas.microsoft.com/mapi/proptag/0x3712001F", f"MyId{img_id}")
            img_body_slide += f"""<p><img src="cid:MyId{img_id}"></p><br>"""
            img_id += 1
        return img_body_slide

    def send(self):
        #<~ Email headers
        self.__mail.To      = 'Liang.Zhang@sony.com' #<~ this is safer
        self.__mail.Subject = f'{self.ppt_name}'
        #<~HTML Body
        email_body_txt: str = """
        <p><font face="Arial">Hello all,</font></p>
        <p><font face="Arial">Pls refer to the attachment or the following hyperlink to see details of {0} result.<br>
        Server: <a href="{1}">SSV Server link</a></font></p>
        """.format(self.ppt_name, self.dst_dir)
        #<~report slides
        email_body_txt += self.attachments()
        email_body_txt += """<p><font face="Arial">Regards,</font></p>"""
        #<~attach ppt file
        self.__mail.Attachments.Add(self.src_ppt_file) # abs path only
        self.__mail.HTMLBody = email_body_txt
        self.__mail.Send()

    def clean(self):
        for file in sorted(pathlib.Path(self.slide_img_folder).glob("*.png")):
            fp: str = str(file.absolute())
            os.remove(fp)

    def quit(self):
        self.clean()
        # self.outlook.Quit() # its not necessary to close outlook app. it askes for trouble

def shoot_img(str_img_name, x1, y1, x2, y2):
    """
    # shoot image for specific area
    take screenshot -> convert to binary data -> put on clipboard
    """
    w = x2 - x1
    h = y2 - y1
    pyautogui.screenshot(str_img_name, region=(x1, y1, w, h))

class ReportPowerPoint:
    """

    this module is specialization for sending PowerPoint Report via email

    it contains the following functionalities:
    - control PowerPoint Application
    - view specific slides in order
    - shoot image for each slide
    - quit PowerPoint Application

    about
    - author: @ZL, 20201223

    """
    __start_slide_no = 2

    def __init__(self, strFile: str, slide_img_folder: str):
        self.__app               = win32.Dispatch('PowerPoint.application')
        self.__app.DisplayAlerts = False
        self.__app.Visible       = True
        self.__prs               = self.__app.Presentations.Open(strFile) # strFile MUST be an absolute path
        self.slide_img_folder    = slide_img_folder

    def quit(self):
        self.__app.Quit()

    def ttl_slides(self):
        return self.__prs.Slides.Count

    def shoot(self):
        for i in range(self.__start_slide_no, self.ttl_slides()):
            self.__prs.Slides(i).Select()
            time.sleep(.5)
            self.__prs.Slides(i).Select()
            shoot_img(f"{self.slide_img_folder}\\slide{i}.png", 444, 278, 1779, 1031) # depends on personal PC and PowerPoint application setting, YMMV

def getSystemInfo():
    """a function retrieves system info

    :return: None
    :rtype: None
    """
    try:
        info = {}
        info['platform']         = platform.system()
        info['platform-release'] = platform.release()
        info['platform-version'] = platform.version()
        info['architecture']     = platform.machine()
        info['hostname']         = socket.gethostname()
        info['ip-address']       = socket.gethostbyname(socket.gethostname())
        info['mac-address']      = ':'.join(re.findall('..', '%012x' % uuid.getnode()))
        info['processor']        = platform.processor()
        info['ram']              = str(round(psutil.virtual_memory().total / (1024.0 **3)))+" GB"
        info['python -V']        = sys.version
        info['screensize']       = ctypes.windll.user32.GetSystemMetrics(0), ctypes.windll.user32.GetSystemMetrics(1)
        return json.dumps(info)
    except Exception as e:
        logging.exception(e)

def is_expired(expired_date: str='Dec 1 2020 8:00AM'):
    """a function decides where current date time ixpired

    :param expired_date: a datetime expired, defaults to 'Dec 1 2020 8:00AM'
    :type expired_date: str, optional
    :return: return true if expired or false if not
    :rtype: bool
    """
    ed = datetime.datetime.strptime(expired_date,'%b %d %Y %I:%M%p')
    now = datetime.datetime.now()
    return (now - ed) > datetime.timedelta(days=1)

def is_outdated(file_path: str, days: int=1):
    mtime = pathlib.Path(file_path).stat().st_mtime
    mtime = datetime.datetime.fromtimestamp(mtime)
    now = datetime.datetime.now()
    return (now - mtime) > datetime.timedelta(days=days)

def merge_LCM_log(fd_path: str):
    """a function merges all LCM log files into one csv file.

    :param fd_path: a folder that stores source csv files
    :type fd_path: the folder path
    """
    if os.path.isdir(fd_path):
        files = glob.glob(os.path.join(fd_path, "*.csv"))
        files.sort()
        n = 84
        col_names = ['h' + str(i) for i in range(n)]
        big_df = pd.concat((pd.read_csv(f, header=None, usecols=range(n), names=col_names, engine='python') \
                                        for f in files), ignore_index=True, sort=False)

        big_df = big_df.dropna(subset=['h' + str(n - 1)])
        fn = os.path.join(fd_path, 'all_LCM_merged.xlsx')
        big_df.to_excel(fn)

def merge_SET_log(fd_path: str):
    """a function merges all SET log files into one csv file.

    :param fd_path: a folder that stores source csv files
    :type fd_path: the folder path
    """
    if os.path.isdir(fd_path):
        files = glob.glob(os.path.join(fd_path, "*.csv"))
        files.sort()
        big_df = pd.concat((pd.read_csv(f, skiprows=1, engine='python') for f in files), ignore_index=True, sort=False)
        big_df = big_df[pd.notnull(big_df['STATE'])]
        #<~ combine all date and save as xl file
        fn = os.path.join(fd_path, 'all_SET_merged.xlsx')
        big_df.to_excel(fn)