import json
import os
import sys
import codecs as cd
import math
import locale
import unicodedata
import datetime
import smtplib
import base64
import ssl
from   logging             import getLogger, config, DEBUG, INFO, WARN, ERROR, CRITICAL
from   email                import encoders
from   email.mime.base      import MIMEBase
from   email.mime.multipart import MIMEMultipart
from   email.mime.text      import MIMEText
from   email.header         import Header
from   email                import charset
from   email.utils          import formatdate
import configparser
import errno
import pandas as pd 
import openpyxl as px
from   openpyxl.styles.fonts import Font
from   openpyxl.worksheet.table import Table, TableStyleInfo

#! import csvExcelUtil as pu

# --------------------------------------------------------
# グラフ変数クラフ（※パラメータ数が多い為、クラスを使用
# --------------------------------------------------------
class graph_data():

    def __init__(self):
        
        self.__ws               = None
        self.__graph_type       = 'BarChart'
        self.__graph_title      = '未設定'
        self.__x_axis_title     = '未設定'
        self.__y_axis_title     = '未設定'
        self.__size_x           = 32
        self.__size_y           = 15
        self.__graph_group      = 'standard'
        self.__graph_style      = 2
        self.__graph_position   = 'B16'
        self.__x_axis_units     = None
        self.__y_axis_units     = None

        #! ref_data     = px.chart.Reference(gd.ws, min_col=2, max_col=max_cell_count-1, min_row=1, max_row=max_row_count)
        #! ref_category = px.chart.Reference(gd.ws, min_col=1, max_col=1,                min_row=2, max_row=max_row_count)
        
        self.__data_min_col     = 1
        self.__data_max_col     = 1
        self.__data_min_row     = 1
        self.__data_max_row     = 1
        
        self.__category_min_col = 1
        self.__category_max_col = 1
        self.__category_min_row = 1
        self.__category_max_row = 1
        
        self.__graph_type_tbl = {
            'LineChart':                '折れ線グラフ',
            'LineChart3D':              '折れ線グラフ3D',
            'BarChart':                 '棒グラフ',
            'BarChart3D':               '棒グラフ3D',
            'AreaChart':                'エリアチャート',
            'AreaChart3D':              'エリアチャート3D',
            'PieChart':                 '円グラフ',
            'ScatterChart':             '散布図・バブルチャート'
        }

        self.__graph_group_tbl = {
            'standard':      '通常',
            'stacked':       '積重ね'
        }

    # ===========================================
    # グラフ・タイプのチェック
    # -------------------------------------------
    def check_graph_type(self, graph_type) -> bool:

        if graph_type in self.__graph_type_tbl.keys():
            return True
        
        return False
    # ===========================================
    # グラフ・グループのチェック
    # -------------------------------------------
    def check_graph_group(self, graph_group) -> bool:

        if graph_group in self.__graph_group_tbl.keys():
            return True
        
        return False

    @property
    def log(self):
        return self.__log
    
    @property
    def ws(self):
        return self.__ws
    
    @ws.setter 
    def ws(self, ws):
        self.__ws = ws

    @property
    def graph_type(self):
        return self.__graph_type
    
    @graph_type.setter 
    def graph_type(self, graph_type):

        if  graph_type in self.__graph_type_tbl.keys():
            self.__graph_type = graph_type

    @property
    def graph_group(self):
        return self.__graph_group

    @graph_group.setter 
    def graph_group(self, graph_group):
        if  graph_group in self.__graph_group_tbl.keys():
            self.__graph_group = graph_group
        
    @property 
    def graph_title(self):
        return self.__graph_title

    @graph_title.setter
    def graph_title(self, graph_title):
        self.__graph_title = graph_title

    @property
    def x_axis_title(self):
        return self.__x_axis_title
    
    @x_axis_title.setter
    def x_axis_title(self, x_axis_title):
        self.__x_axis_title = x_axis_title

    @property
    def y_axis_title(self):
        return self.__y_axis_title

    @y_axis_title.setter
    def y_axis_title(self, y_axis_title):
        self.__y_axis_title = y_axis_title

    @property
    def size_x(self):
        return self.__size_x

    @size_x.setter
    def size_x(self, size_x):
        self.__size_x = size_x

    @property 
    def size_y(self):
        return self.__size_y

    @size_y.setter 
    def size_y(self, size_y):
        self.__size_y = size_y

    @property
    def graph_style(self):
        return self.__graph_style

    @graph_style.setter
    def graph_style(self, graph_style):
        self.__graph_style = graph_style
    
    @property
    def graph_position(self):
        return self.__graph_position

    @graph_position.setter
    def graph_position(self, graph_position):
        self.__graph_position = graph_position

    @property
    def x_axis_units(self):
        return self.__x_axis_units

    @x_axis_units.setter
    def x_axis_units(self, x_axis_units):
        self.__x_axis_units = x_axis_units

    @property
    def y_axis_units(self):
        return self.__y_axis_units

    @y_axis_units.setter 
    def y_axis_units(self, y_axis_units):
        self.__y_axis_units = y_axis_units

    @property 
    def data_min_col(self):
        return self.__data_min_col
    
    @data_min_col.setter
    def data_min_col(self, data_min_col):
        self.__data_min_col = data_min_col

    @property
    def data_max_col(self):
        return self.__data_max_col
    
    @data_max_col.setter
    def data_max_col(self, data_max_col):
        self.__data_max_col = data_max_col
    
    @property
    def data_min_row(self):
        return self.__data_min_row
    
    @data_min_row.setter
    def data_min_row(self, data_min_row):
        self.__data_min_row = data_min_row

    @property
    def data_max_row(self):
        return self.__data_max_row

    @data_max_row.setter
    def data_max_row(self, data_max_row):
        self.__data_max_row = data_max_row

    @property
    def categotry_min_col(self):
        return self.__category_min_col

    @categotry_min_col.setter
    def category_min_col(self, category_min_col):
        self.__category_min_col = category_min_col

    @property
    def category_max_col(self):
        return self.__category_max_col

    @category_max_col.setter
    def category_max_col(self, category_max_col):
        self.__category_max_col = category_max_col
    
    @property
    def category_min_row(self):
        return self.__category_min_row
    
    @category_min_row.setter
    def category_min_row(self, category_min_row):
        self.__category_min_row = category_min_row

    @property 
    def category_max_row(self):
        return self.__category_max_row
    
    @category_max_row.setter
    def category_max_row(self, category_max_row):
        self.__category_max_row = category_max_row

        

class csv_excel_pivot:
    class csv_excel_pivot_error(Exception):
        pass

    class csv_excel_pivot_not_exists(Exception):
        pass

    class csv_excel_pivot_exists(Exception):
        pass
    
    def __init__(self, log):
        
        # =================================
        # 構成ファイルの読込み
        # ---------------------------------
        execute_dir = os.path.dirname(__file__)
        config_ini      = configparser.ConfigParser()
        config_ini_path = f"{execute_dir}/config.ini"
        if  not os.path.exists(config_ini_path):
            raise FileNotFoundError(errno.ENOENT, os.strerror(errno.ENOENT), config_ini)
    
        config_ini.read(config_ini_path, encoding='utf-8')

        # =================================
        # Logger 設定
        # ---------------------------------
        self.__log = log
    

    @property
    def log(self):
        return self.__log

    # ---------------------------------------------------------------
    # 年月日を年月に変換する
    # ---------------------------------------------------------------
    def insert_ym(self, csv_data, field_name, field_insert_name, val) -> None:

        #! column_list:[str] = csv_data.columns.values
        #! column_list:[str] = csv_data.columns.to_list()
        self.log.log(DEBUG, "========================")
        self.log.log(DEBUG, f"field Name = {field_name}, field insert name = {field_insert_name}, val = {val}")
        self.log.log(DEBUG, "------------------------")
        column_list = csv_data.columns.to_list()
        if  field_name not in column_list:
            self.log.log(DEBUG, f"カラムリスト＝{column_list}")
            raise self.csv_excel_pivot_not_exists("カラム名が見つかりません。")
        
        if  field_insert_name in column_list:
            raise self.csv_excel_pivot_exists("追加するカラム名は既に存在しています。")
        
        insert_loc = csv_data.columns.get_loc(field_name)       # 変換対象となる列名の位置を取得する

        self.log.log(DEBUG, f"insert loc = {insert_loc}, field_insert_name = {field_insert_name}")
        
        csv_data.insert(insert_loc, field_insert_name, 0)       # 変換した年月を格納する列を追加する
        self.log.log(DEBUG, f"insert_loc = {insert_loc}, insert_fieldName = {field_insert_name}")
        
            
        for i, row in csv_data.iterrows():
            src_data = csv_data.at[i, field_name]
            yymm = str(int(src_data/val))                # 年月日を変換
            #! tar_data = yymm[:4] + "年" + yymm[4:] + "月"
            tar_data = f"{yymm[:4]}{yymm[4:]}"
            
            #! self.log.log(DEBUG, f"insert Data = {tar_data}")
            csv_data.at[i, field_insert_name] = tar_data

    # ---------------------------------------------------------------
    # ピボットテーブル（集計）を作成する
    # ---------------------------------------------------------------
    def create_pivot_table(self, csv_data, v_field, h_field, sum_field, sum_type, total_flag, total_name, cumulative_flag):

        aggfunc_tbl = {
            "sum":      "合計",
            "len":      "件数",
            "min":      "最小値",
            "max":      "最大値",
            "median":   "中央値",
            "mean":     "平均値",
            "std":      "標準偏差"
        }

        p_aggfunc = ''

        if  sum_type in aggfunc_tbl.keys():
            p_aggfunc = sum_type
        else:
            p_aggfunc = 'sum'
        
        if  total_flag == False:
            sales = pd.pivot_table(csv_data, index=v_field, columns=h_field, values=sum_field, aggfunc=p_aggfunc, fill_value=0, margins=False) # 合計なし
        else:
            sales = pd.pivot_table(csv_data, index=v_field, columns=h_field, values=sum_field, aggfunc=p_aggfunc, fill_value=0, margins=True, margins_name=total_name) # 合計あり
            
        if  cumulative_flag == True:
            sales = sales.cumsum()  # 累計集計に変更する
        
        return sales
    
    # ---------------------------------------------------------------
    # グラフを作成する
    # ---------------------------------------------------------------
    def create_graph(self, gd: graph_data) -> None:
        # ======================= 
        # グラフ構成オブジェクト
        # -----------------------
        # x_axis:       横軸オブジェクト
        # y_axis:       縦軸オブジェクト
        # Series:       グラフ描画オブジェクト
        #  |_ smooth:     |_滑らかな線を描画： True, False
        #  |_ Line:       |_グラフ描画オブジェクトの線
        #  |_ Marker:     |_グラフ描画オブジェクトのマーカー
        #  |_ labels:     |_グラフ描画オブジェクトのデータラベル（DataLabelListオブジェクト）

        graph_type_tbl = {
            'LineChart':                '折れ線グラフ',
            'LineChart3D':              '折れ線グラフ3D',
            'BarChart':                 '棒グラフ',
            'BarChart3D':               '棒グラフ3D',
            'AreaChart':                'エリアチャート',
            'AreaChart3D':              'エリアチャート3D',
            'PieChart':                 '円グラフ',
            'ScatterChart':             '散布図・バブルチャート'
        }
        max_row_count  = gd.ws.max_row       # ワークシートの最大行数
        max_cell_count = gd.ws.max_column    # ワークシートの最大列数

        if  gd.graph_type in graph_type_tbl.keys():
            if   gd.graph_type == 'LineChart':
                chart = px.chart.LineChart()
            elif gd.graph_type == 'LineChart3D':
                chart = px.chart.LineChart3D()
            elif gd.graph_type == 'BarChart':
                chart = px.chart.BarChart()
            elif gd.graph_type == 'BarChart3D':
                chart = px.chart.BarChart3D()
            elif gd.graph_type == 'AreaChart':
                chart = px.chart.AreaChart()
            elif gd.graph_type == 'AreaChart3D':
                chart = px.chart.AreaChart3D()
            elif gd.graph_type == 'PieChart':
                chart = px.chart.PieChart()
            elif gd.graph_type == 'ScatterChart':
                chart = px.chart.ScatterChart()
        else:
            chart = px.chart.LineChart()
        
        #! chart.y_axis.get_major_formatter().set_useOffset(False) # 指数表示を行わない
        #! chart.style=19

        if   gd.graph_group in 'standard':
             chart.grouping =  gd.graph_group
        elif gd.graph_group == 'stacked':
             chart.grouping =  gd.graph_group
             chart.overlap  =  100           # 積み重ねグラフにした時、overlap=100を指定しないと積み重ねグラフにならない
        else:
            chart.grouping = "standard"
        
        # グラフの表示を整える
        chart.style         = gd.graph_style
        chart.title         = gd.graph_title
        chart.y_axis.title  = gd.y_axis_title
        chart.x_axis.title  = gd.x_axis_title
        chart.height        = gd.size_y
        chart.width         = gd.size_x
    
        self.log.log(DEBUG, f"max_col = {max_cell_count}, max_row = {max_row_count}")
        self.log.log(DEBUG, f"Data     min_col={gd.data_min_col},     max_col={gd.data_max_col},     min_row={gd.data_min_row},     max_row={gd.data_max_row}")
        self.log.log(DEBUG, f"Category min_col={gd.category_min_col}, max_col={gd.category_max_col}, min_row={gd.category_min_row}, max_row={gd.category_max_row}")
        
        #! ref_data     = px.chart.Reference(gd.ws, min_col=2, max_col=max_cell_count-1, min_row=1, max_row=max_row_count)
        #! ref_category = px.chart.Reference(gd.ws, min_col=1, max_col=1,                min_row=2, max_row=max_row_count)
        ref_data        = px.chart.Reference(gd.ws, min_col=gd.data_min_col,     max_col=gd.data_max_col,
                                                    min_row=gd.data_min_row,     max_row=gd.data_max_row)
        ref_category    = px.chart.Reference(gd.ws, min_col=gd.category_min_col, max_col=gd.category_max_col,
                                                    min_row=gd.category_min_row, max_row=gd.category_max_row)
        chart.add_data(ref_data, titles_from_data=True)
        chart.set_categories(ref_category)

        # =========================================================
        # 軸のラベルの表示単位変更
        # =========================================================
        axis_units = {
            'hundreds':         '     100単位',
            'thousands':        '   1,000単位',
            'tenThousands':     '     1万単位',
            'hundredThousands': '    10万単位',
            'millions':         '   100万単位',
            'tenMillions':      ' 1,000万単位',
            'hundredMillions':  '     1億単位',
            'billions':         '    10億単位'
        }
        
        if  gd.x_axis_units is not None and gd.x_axis_units in axis_units.keys():
            chart.x_axis.dispUnits = px.chart.axis.DisplayUnitsLabelList(builtInUnit=gd.x_axis_units) # 横軸の表示を変更する

        if  gd.y_axis_units is not None and gd.y_axis_units in axis_units.keys():
            chart.y_axis.dispUnits = px.chart.axis.DisplayUnitsLabelList(builtInUnit=gd.y_axis_units) # 縦軸の表示を変更する
        
        # =========================================================
        # マーカー設定 サンプルコード
        # ---------------------------------------------------------
    
        # [C] 各系列ごとにマーカとラインを設定する
        #! markType = ['plus','diamond', 'square', 'dash', 'dot', 'x', 'auto', 'circle', 'star', 'picture', 'triangle']
        #! lineType = ['sysDashDot', 'dashDot', 'sysDash', 'dash', 'dot', 'lgDashDotDot', 'lgDashDot', 'sysDot', 'sysDashDotDot', 'solid', 'lgDash']
        #! mark_i = 0
        #! type_i = 0
    
        for ser in chart.series:
            #! ser.marker.symbol = markType[mark_i]
            ser.marker.symbol = 'auto'
            ser.marker.size   = 12
            #! ser.smooth = True
            #! print(ser.marker.symbol)
            #! mark_i += 1
            #! if  mark_i >= len(markType):
            #!     mark_i = 0
        
        
        # =========================================================
        # マーカー設定 サンプルコード
        # ---------------------------------------------------------
        # ser1=chart.series[0]

        # マーカー設定
        #! ser1.marker.symbol = 'circle'     # マーカーの形状(円)を選択
        #! ser1.marker.size = 8              # マーカーの大きさを設定(整数)
        #! ser1.marker.graphicalProperties.solidFill = 'FF0000'       # マーカーの塗り潰し色を設定
        #! ser1.marker.graphicalProperties.line.solidFill = '0F0F0F'  # マーカーの枠線の色を設定


        # <系列2>のSeriesオブジェクト -----------------------------------------------------------
        #! ser2=chart.series[1]

        # マーカーの設定
        #! ser2.marker.symbol = 'diamond'    # マーカーの形状(ひし形)を選択
        #! ser2.marker.size = 9              # マーカーの大きさを設定(整数)
        #! ser2.marker.graphicalProperties.solidFill = '0202F2'       # マーカーの塗り潰し色を設定
        #! ser2.marker.graphicalProperties.line.solidFill = '00F0F'   # マーカーの枠線の色を設定


        # <系列3>のSeriesオブジェクト -----------------------------------------------------------
        #! ser3=chart.series[2]

        # マーカーの設定
        #! ser3.marker.symbol = 'triangle'   # マーカーの形状(三角形)を選択
        #! ser3.marker.size = 9              # マーカーの大きさを設定(整数)
        #! ser3.marker.graphicalProperties.solidFill = '00FF11'       # マーカーの塗り潰し色を設定
        #! ser3.marker.graphicalProperties.line.solidFill = '00F0F'   # マーカーの枠線の色を設定

        # ==================================================================
        # 折れ線グラフの描画
        # ------------------------------------------------------------------
        #! chart2 = px.chart.LineChart()
        #! chart2.style=19 
        #! chart.grouping = "stacked"
        #! chart2.grouping = "standard"
        
        #プログラム10｜グラフの表示を整える
        #! chart2.style = 20
        #! chart2.overlap = 100
        #! chart2.title = '月別受注金額'
        #! chart2.y_axis.title = '金額'
        #! chart2.x_axis.title = '年月'
        
        #! chart2.height = 15
        #! chart2.width = 32

        #! v2 = px.chart.Reference(ws, min_col=2, max_col=12, min_row=1, max_row=12)
        #! h2 = px.chart.Reference(ws, min_col=1, max_col=1, min_row=2, max_row=12)
        #! chart2.add_data(v2, titles_from_data=True)
        #! chart2.set_categories(h2)

        #! chart2.y_axis.axId = 200  # Y第2軸の形成に関わっているようで実行しないとY第2軸が形成されない

        #! chart2.y_axis.crosses = 'max'  # Y第2軸を右に移動する命令らしい


        # 別々に作成したY第1軸とY第2軸を1つにする

        #! chart += chart2  # 支払額のY第1軸と単価のY第2軸のデータを合成する


        #プログラム11｜グラフを表示
        gd.ws.add_chart(chart, gd.graph_position)
