#!/usr/bin/python
# -*- coding: utf-8 -*-
"""
MPlotJupyter.py
uGao.MPlot I/F on Jupyter and I/F of the dark mode
"""
from __future__ import print_function
import os
import Manyo
import six

from uGao.uGaoUtil import IFEvtProp, ConvertEC, TraceAtt
from uGao.FastPlotQt import Chart, PlotData
import matplotlib
ISJUPYTER = False
if matplotlib.get_backend() == "inline":
    ISJUPYTER = True
    import matplotlib.pyplot as plt
else:
    from matplotlib.figure import Figure
    from matplotlib.backends.backend_agg import FigureCanvasAgg as FigCanvas

"""
CUI to control FastPlot on Jupyter or with non-X-server environment

Sample code:
    import Manyo
    import uGao.MPlotJupyter as FP

    ec = Manyo.ElementContainer()
    ec.Add("X", [1.0, 2.0, 3.0, 4.0, 5.0])
    ec.Add("Y", [1.0, 2.0, 0.1, 1.0])
    ec.Add("E", [1.0, 1.0, 1.0, 1.0])
    ec.SetKeys("X", "Y", "E")

    ec2 = ec.Mul(2.0)

    p = FP.MPlotJupyter()
    p.AddData([ec, ec2])

    p.SetYScale(False, 0.01, 10)
    p.SetLog(True)
    p.SetXScale(False, -1, 10)
    #p.RemoveData(1)
    p.SetPlotParam(0, ls="-", lc="k", lw=9)
    p.SetMainTitle("Run No: 5515")
    p.SetSubTitle("This is test!")
    p.SetXLabel("Index X")
    p.SetYLabel("Intensity")

    # Save Plot figure as a file
    p.Save("./test.png")
    # Plot on Jupyter
    p.Show()
"""

#######################################
#  MPlotJupyter
#######################################
class MPlotJupyter(object):
    def __init__(self, data=None, order=1, fTitle=None, sizeX=8.0, sizeY=5.0, dpi=90):
        """
        """
        self.order = order
        self.dpi = dpi
        # データオブジェクトを設定
        self.dataobj = PlotData(self, None, order)
        # キャンバスを作成
        if ISJUPYTER:
            print("is Jupyter")
            self.plt = plt
            self.fig = plt.figure()
            self.canvas = self.fig.canvas
        else: # normal inDark mode
            print("is Dark mode ")
            self.fig = Figure((sizeX, sizeY), dpi)
            self.canvas = FigCanvas(self.fig)

        Chart(self, self.dataobj)

        # プロッタが公開しているインターフェイスクラスのインスタンスを取得
        self.ifi = IFEvtProp(order)

        if data is not None:
            self.AddData(data)

    ###################################################
    def Show(self):
        """
        """
        if ISJUPYTER:
            self.plt.show()
        else:
            pass
    ###################################################
    def AddData(self, data):
        """
        データ追加イベント発行
        @param  data (ElementContainer or list of ElementContainer)
        @retval None
        """
        ce = ConvertEC()
        lstDat = []
        # リストか
        if isinstance(data, list):
            # リスト中のエレメントコンテナ毎に変換

            for ec in data:
                ret = ce.ConvertEC(ec)
                if ret is None:
                    print("\nArgument is not acceptable.")
                    return
                # Manyo データからPython データタイプへ変換
                lstDat.append(ret)
        # 単一データ
        else:
            # Manyo データからPython データタイプへ変換
            ret = ce.ConvertEC(data)
            if ret is None:
                print("\nArgument is not acceptable.")
                return
            lstDat.append(ret)

        self.ifi.NotifyEvent(self, 'add', lstDat)

    ###################################################
    def Remove(self, fignum):
        """
        データ削除イベント発行
        @param  fignum (int) index of figure
        @retval None
        """
        # データ数取得
        dno = self.ifi.GetProperty("datanum")
        # 指定されたデータ数は、実際のデータ数以内か
        if dno >= int(fignum):
            # データ削除イベント発行
            self.ifi.NotifyEvent(self, "remove", int(fignum))

    ###################################################
    def ChangeDataOrder(self, swap1, swap2):
        """
        データ順番変更イベント発行
        @param  swap1 (int) figure index to swap
        @param  swap2 (int) figure index to swap
        @retval None
        """
        # データ数取得
        dno = self.ifi.GetProperty("datanum")
        # 指定されたデータ数は、実際のデータ数以内か
        if dno >= int(swap1) and dno >= int(swap2):
            # データ順番変更イベント発行
            self.ifi.NotifyEvent(self, "change", (int(swap1), int(swap2)))
        else:
            # 否定応答を返信
            return "NACK\n"

    ###################################################
    def ChangeLogMode(self, axis, flag):
        """
        ログモード変更
        @param axis (str) "X" or "Y" for applying log scale
        @retval None
        """
        if axis.upper() == "X":
            self.ifi.NotifyEvent(self, "logx", flag)
        elif axis.upper() == "Y":
            self.ifi.NotifyEvent(self, "log", flag)

    #########################################
    def SetLog(self, flag=None):
        """
        Y-axisログモード設定
        @param  flag (bool) Log scale for Y axis
        @retval None
        """
        if isinstance(flag, bool):
            self.ChangeLogMode("Y", flag)
    #########################################
    def SetLogX(self, flag=None):
        """
        X-axisログモード設定
        @param  flag (bool) Log scale for X axis
        @retval None
        """
        if isinstance(flag, bool):
            self.ChangeLogMode("X", flag)
    #########################################
    def SetLogY(self, flag=None):
        """
        Y-axisログモード設定
        @param  flag (bool) Log scale for Y axis
        @retval None
        """
        if isinstance(flag, bool):
            self.ChangeLogMode("Y", flag)

    ###################################################
    def SetXScale(self, isAuto, min_X, max_X):
        """
        Set scale for X-axis
        @param isAuto (bool) True means Auto scale for X axis
        @param min_X (float) minimum value of X scale when isAuto is False
        @param max_X (float) maximum value of X scale when isAuto is False
        @retval None
        """

        if isAuto:
            # スケール変更(自動)イベント発行
            self.ifi.NotifyEvent(self, "xscale", (True,))
        else:
            # マニュアルスケール範囲、X軸範囲または Y軸範囲
            self.ifi.NotifyEvent(
                self, "xscale", (False, float(min_X), float(max_X)))
    ###################################################
    def SetYScale(self, isAuto, min_Y, max_Y):
        """
        Set scale for Y-axis
        @param isAuto (bool) True means Auto scale for Y axis
        @param min_Y (float) minimum value of Y scale when isAuto is False
        @param max_Y (float) maximum value of Y scale when isAuto is False
        @retval None
        """
        if isAuto:
            # スケール変更(自動)イベント発行
            self.ifi.NotifyEvent(self, "yscale", (True,))
        else:
            # マニュアルスケール範囲、X軸範囲または Y軸範囲
            self.ifi.NotifyEvent(
                self, "yscale", (False, float(min_Y), float(max_Y)))

    #########################################
    def SetOffset(self, xoffset=None, yoffset=None):
        """
        オーバレイ時のオフセット指定
        @param  xoffset (int)  X軸オフセット
        @param  yoffset (int)  Y軸オフセット
        @retval 無し
        """
        # オフセットを入力していない場合
        if xoffset is None or yoffset is None:
            self._EmptyName('XOffset and YOffset')
        # 整数変換できるか
        else:
            try:
                xo = int(xoffset)
                yo = int(yoffset)
            except:
                print("\nError!!  Arguments must be integer.")

            # Xオフセット範囲チェック
            if xo < 0 or xo > 200:
                print("\nError!!  Offset must be between 0 to 200.")
                return
            # Yオフセット範囲チェック
            if yo < 0 or yo > 200:
                print("\nError!!  Arguments must be between 0 to 200.")
                return
            # コマンド作成
            self.ifi.NotifyEvent(
                self, "offset", (xoffset, yoffset))

    #########################################
    def SetPlotParam(self, dnum=None, **kargs):
        """
        プロットパラメータ指定
        @param  dnum     データNo  または "all"
        @param  **kargs   キーワード引数
                ls        線種　['-', ':','--', '-.','']
                hs        ヒストグラムスタイル　True or False
                lw        線幅　1〜10
                lc        線色　['*', 'b', 'g', 'r', 'c', 'm', 'y', 'k']
                mk        マーカーの種類  ['', '.','o','s','v','d','+','x']
                ms        マーカーのサイズ　1〜10
                mc        マーカーの色　['*', 'b', 'g', 'r', 'c', 'm', 'y', 'k']
                eb        エラーバー指定    True or False
                es        エラーバーのキャップサイズ　1〜10
                sc        エラーバーの色　['*', 'b', 'g', 'r', 'c', 'm', 'y', 'k']
        @retval 無し
        """
        # データ番号を入力しなかった場合
        if dnum is None:
            self._EmptyName('Data Number')
        else:
            # 整数変換できるか
            try:
                dno = int(dnum)
            except:
                print('\nError data number!! The argument 1 must be integer.')
                return
            # キーワード引き数の数を確認
            if len(kargs) == 0:
                print("\nError!!  It needs at least one keyword.")
                print(
                    'Available keywords are "ls", hs", "lw", "lc", "mk", "ms", "mc", "eb", "es" and "ec".')
                return
            print(kargs)
            # キーワードを取得
            for key in kargs:
                # 線種の指定か
                if key == "ls":
                    if not self._CheckValue(TraceAtt.linestyle, key, kargs[key]):
                        return
                # ヒストグラムスタイル指定か
                elif key == "hs":
                    if type(kargs[key]) != bool:
                        print('\nError!! "{}" requires True/False'.format(key))
                        return
                # 線幅の指定か
                elif key == "lw":
                    if not self._CheckNum(key, kargs[key]):
                        return
                # 線色の指定か
                elif key == "lc":
                    if not self._CheckValue(TraceAtt.color, key, kargs[key]):
                        return
                # マーカの指定か
                elif key == "mk":
                    if not self._CheckValue(TraceAtt.marker, key, kargs[key]):
                        return
                # マーカーサイズの指定か
                elif key == "ms":
                    if not self._CheckNum(key, kargs[key]):
                        return
                # マーカー色の指定か
                elif key == "mc":
                    if not self._CheckValue(TraceAtt.color, key, kargs[key]):
                        return
                # エラーバー指定か
                elif key == "eb":
                    if type(kargs[key]) != bool:
                        print('\nError!! "{}" requires True/False'.format(key))
                        return
                # キャップサイズの指定か
                elif key == "es":
                    if not self._CheckNum(key, kargs[key]):
                        return
                # エラーバー色の指定か
                elif key == "ec":
                    if not self._CheckValue(TraceAtt.color, key, kargs[key]):
                        return
                # キーワードエラー
                else:
                    print("\nError Keyword !!")
                    print(
                        'Available keywords are "ls", "lw", "lc", "mk", "ms", "mc", "eb", "es" and "ec".')
                    return

            self.ifi.NotifyEvent(self, "plotparam", (dno, kargs))
    #########################################
    def _CheckValue(self, lst, key, value):
        """
        指定されたキーの値をチェック
        @param  lst 　有効な値のリスト
        @param  key   キー文字列
        @param  value 値の文字列
        @retval True/False
        """
        # 指定された値はリスト中に存在するか
        if value in lst:
            # リスト中の値の順番を探す
            i = 0
            for item in lst:
                # リスト中にあるか
                if item == value:
                    return True
                i += 1

        # 不正な値だったなら
        else:
            strMsg = "\nError value : Avairable for {} are ".format(key)
            # 指定可能な値を列挙
            # 最後の2個の値を除き、カンマ付きで列挙
            for i in range(len(lst) - 2):
                item = '"%s", ' % lst[i]
                strMsg = strMsg + item
            # 最後の2個を連結
            i += 1
            j = i + 1
            item = '"%s" and "%s"' % (lst[i], lst[j])
            strMsg = strMsg + item
            print(strMsg)
            return False
    #########################################
    def _CheckNum(self, key, value):
        """
        指定されたキーの値をチェック
        @param  key   キー文字列
        @param  value 値
        @retval True/False
        """
        if type(value) is not int:
            print('\nError!!  Value for "%s" must be integer.' % key)
            return False
        if value < 1 or value > 10:
            print('\nError!! Value for "%s" must be between 1 to 10.' % key)
            return False

        return True
    #########################################
    def _EmptyName(self, name):
        """
        引数を入力しなかった場合入力を促すメッセージ表示
        @param name 入力項目
        @retval 無し
        """
        print("\nMissing argument error!! Input %s." % name)

    #########################################
    def Save(self, fname=None):
        """
        画像ファイルの保存
        @param  ファイル名
        @retval 無し
        """
        # 引数無し時のプロテクト
        if fname is None:
            self._EmptyName('filename')
            return
        try:
            six.u(fname)
        except:
            # 全角文字が含まれている場合エラーとする
            self._AsciiName('Filename')
            return
        work = fname.split(os.sep)
        print(work)
        # ファイル名取り出し
        filename = os.path.basename(fname)
        path, ext = os.path.splitext(filename)
        # 拡張子を除くファイル名の長さは1以上か
        if len(path) > 1:
            # サポートしている拡張子か
            if ext == ".png" or ext == ".ps" or ext == ".eps":
                self.canvas.print_figure(fname)
            else:
                print("\nError!!  Extention must be png, ps, or eps.")
        else:
            print("\nError!! Invalid file name.")

    #########################################
    def LoadFromText(self, filepath):
        """
        Load data from text file saved as MPlot
        @param filepath (str) path to file to load
        @retval None
        """
        if os.path.exists(filepath):
            self.ifi.NotifyEvent(self, "loaddatafromtext", filepath)
        else:
            raise UserWarning(
                "MPlot::LoadFromText >>> Not found such file={}".format(filepath))
    #########################################
    def SaveAsText(self, filepath, isHist=False, useMask=False, maskVal=None):
        """
        Save data as text file
        @param filepath (str) path to file
        @param isHist (bool) save text as histogram
        @param useMask (bool) Use mask value user defines
        @param maskVal (float) mask value user defines
        @retval None
        """
        p_isHist = "FALSE"
        p_useMask = "FALSE"
        p_maskVal = "NONE"
        if useMask:
            p_useMask = "TRUE"
            if maskVal is not None:
                p_maskVal = "%g".format(float(maskVal))

        self.ifi.NotifyEvent(self, "savedataastext", (filepath, p_isHist, p_useMask, p_maskVal))

    #########################################
    def SetXLabel(self, xlabel=None):
        """
        X軸のラベル設定
        @param  xlabel (string) word for X axis label
        @retval None
        """
        if xlabel is None or xlabel == "":
            self._EmptyName('xlabel')
        else:
            try:
                # 半角英数字を入力したか
                six.u(xlabel)
            except:
                self._AsciiName('xlabel')
            else:
                # コマンド作成
                self.ifi.NotifyEvent(self, "scalelabel", (xlabel, "*"))
    #########################################
    def SetYLabel(self, ylabel=None):
        """
        X軸のラベル設定
        @param  ylabel (string) word for Y axis label
        @retval None
        """
        if ylabel is None or ylabel == "":
            self._EmptyName('ylabel')
        else:
            try:
                # 半角英数字を入力したか
                six.u(ylabel)
            except:
                self._AsciiName('ylabel')
            else:
                # コマンド作成
                self.ifi.NotifyEvent(self, "scalelabel", ("*", ylabel))

    #########################################
    def SetMainTitle(self, main=None):
        """
        メインタイトル設定
        @param  main (str) Main title word
        @retval None
        """
        if main is None or main == "":
            self._EmptyName('MainTitle')
        else:
            try:
                # 半角英数字を入力したか
                six.u(main)
            except:
                self._AsciiName('MainTitle')

            else:
                # コマンド作成
                self.ifi.NotifyEvent(self, "title", (main, "*"))
    #########################################
    def SetSubTitle(self, sub=None):
        """
        サブタイトル設定
        @param  sub (str) Sub title word
        @retval None
        """
        if sub is None or sub == "":
            self._EmptyName('SubTitle')
        else:
            try:
                # 半角英数字を入力したか
                six.u(sub)
            except:
                self._AsciiName('SubTitle')
            else:
                # コマンド作成
                self.ifi.NotifyEvent(self, "title", ("*", sub))

    #########################################
    def _AsciiName(self, name):
        """
        Ascii文字入力を促すメッセージ表示
        @param name (string) The name of key
        @retval None
        """
        print("\nCode error!! %s must be ASCII string." % name)
