# -*- coding: utf-8 -*-
#-------------------------------------------------------------------------------
# Name:        SliceViewer
# Purpose:
#
# Author:
#
# Created:     22/01/2016
# Copyright:   (c)
# Licence:
#-------------------------------------------------------------------------------

#######################################
#  SliceViewer
#######################################

import thread
import os, sys
import socket
import time
import threading
import Queue
import subprocess

class SliceViewer(object) :
    """
    プロッタのファサードクラス
    Python インタープリタ からプロッタを起動し
    制御するためのユーザインターフェイスを提供
    """
    # プロッタ通信クラスのインスタンスリスト
    plotList = []
    # plotList のロック用
    lck = thread.allocate_lock()

    #########################################
    def __init__(self,data=None, closeFlag = True):
        """
        コンストラクタ
        @param  data  プロッタに表示するデータ、省略可
        　　　　エレメントアレイまたはエレメントアレイのリスト
        @closeFlag   親プロセス終了時に、プロッタをクローズするか
        　　　　　　 否かのフラグ
        @retval 無し
        """
        self.plot = None

        # プロッタの作成
        self.NewPlot(data, closeFlag)

    #########################################
    def NewPlot(self, data=None, closeFlag = True):
        """
        プロッタを追加し、カレントプロッタとする
        @param  data  プロッタに表示するデータ、省略可
        　　　　エレメントアレイまたはエレメントアレイのリスト
        @param closeFlag  親プロセス終了時に、子プロセスを終了させるかどうかのフラグ
        @retval 無し
        """
        # 引数リストを作成
        pno = len(self.plotList) +1
        strPno = "%d" % pno

        if closeFlag == True:
            cflag = "True"
        else:
            cflag = "False"

        arg = ["SliceViewerMain.py", strPno, cflag]
        # プロセスID とプロッタ番号からポート番号を生成
        pxx = (os.getpid() % 500)*20
        portno = 10000+ pxx + pno

        # GUI通信クラスのインスタンス取得
        p = CommWx()

        # プロセス生成と通信接続に成功したなら
        if p.NewComm(portno, arg):
            # 旧カレントプロッタのアイコン化要求
            #self._IconizeCurrent()

            self.lck.acquire()
            # 追加したプロッタをリストへ追加
            #self.plotList.append(p)
            # ロックの解放
            self.lck.release()

            # 追加したプロッタをカレントプロッタとする。
            self.plot = p

            # データが指定されていたら
            if not data == None:
                # データを追加
                self.AddData(data)
        else:
            print "\nError!!  Cannot create a new plotter!!."

#######################################
#  User Command
#######################################
# コマンドヘルプ
    def help(self):
        print u"*---------------------------"
        print u"*** Slice Viewer Command ***"
        print u"*---------------------------"
        print u"+ setFocalPoint(x, y, z)"
        print u"+ setViewPoint(x, y, z)"
        print u"+ setDistance(dist)"
        print u"+ setAzimuth(az)"
        print u"+ setElevation(ele)"
        print u"+ setScalarRange(min, max)"
        print u"+ setAxisLength(lenX, lenY, lenZ)"
        print u"+ setGridScale(scaleX, scaleY, scaleZ)"
        print u"+ setOutputDirectory(directory)"
        print u"+ doSliceAx1(proc, pos, delta, ax2bin, ax3bin)"
        print u"  - proc:"
        print u"      0: Draw on main view"
        print u"      1: Draw by M2Plot"
        print u"      2: Save as Image (should setOutputDirectory)"
        print u"      3: Save as ECA (should setOutputDirectory)"
        print u"  - pos: position of plane"  
        print u"  - delta: thickness of plane"
        print u"  - ax2bin, ax3bin: bin for each direction"
        print u"+ doSliceAx2(proc, pos, delta, ax3bin, ax1bin)"
        print u"  - proc:"
        print u"      0: Draw on main view"
        print u"      1: Draw by M2Plot"
        print u"      2: Save as Image (should setOutputDirectory)"
        print u"      3: Save as ECA (should setOutputDirectory)"
        print u"  - pos: position of plane"  
        print u"  - delta: thickness of plane"
        print u"  - ax1bin, ax3bin: bin for each direction"
        print u"+ doSliceAx3(proc, pos, delta, ax1bin, ax2bin)"
        print u"  - proc:"
        print u"      0: Draw on main view"
        print u"      1: Draw by M2Plot"
        print u"      2: Save as Image (should setOutputDirectory)"
        print u"      3: Save as ECA (should setOutputDirectory)"
        print u"  - pos: position of plane"  
        print u"  - delta: thickness of plane"
        print u"  - ax1bin, ax2bin: bin for each direction"
        print u"+ doFreeSlice(proc, org[0], org[1], org[2], x[0], x[1], x[2], "
        print u"              y[0], y[1], y[2], xbin, ybin, xrange[0], xrange[1], yrange[0], yrange[1], delta)"
        print u"  - proc:"
        print u"      0: Draw on main view"
        print u"      1: Draw by M2Plot"
        print u"      2: Save as Image (should setOutputDirectory)"
        print u"      3: Save as ECA (should setOutputDirectory)"
        print u"  - org: Origin of plane"  
        print u"  - x: point on x-axis"
        print u"  - y: point on y-axis"
        print u"  - xbin, ybin: bin for each direction"
        print u"  - xrange, yrange: view range for each direction"
        print u"  - delta: thickness of plane"


# 注視点を指定
    def setFocalPoint(self, x, y, z):
        strCmd = "setFocalPoint %s %s %s\n" % (str(x), str(y), str(z))
        self.plot.PutCommand(strCmd)

# 注視点を指定
    def setViewPoint(self, x, y, z):
        strCmd = "setViewPoint %s %s %s\n" % (str(x), str(y), str(z))
        self.plot.PutCommand(strCmd)
        
# 視点の距離を指定
    def setDistance(self, dist):
        strCmd = "setDistance %s\n" % str(dist)
        self.plot.PutCommand(strCmd)

# 方位角を指定
    def setAzimuth(self, az):
        strCmd = "setAzimuth %s\n" % str(az)
        self.plot.PutCommand(strCmd)
        
# 視点の仰角を指定
    def setElevation(self, ele):
        strCmd = "setElevation %s\n" % str(ele)
        self.plot.PutCommand(strCmd)        

# set scalar range
    def setScalarRange(self, min, max):
        strCmd = "setScalarRange %s %s\n" %(str(min), str(max))
        self.plot.PutCommand(strCmd)

# set axis length
    def setAxisLength(self, lenX, lenY, lenZ):
        strCmd = "setAxisLength %s %s %s\n" %(str(lenX), str(lenY), str(lenZ))
        self.plot.PutCommand(strCmd)

# set grid scale
    def setGridScale(self, scaleX, scaleY, scaleZ):
        strCmd = "setGridScale %s %s %s\n" %(str(scaleX), str(scaleY), str(scaleZ))
        self.plot.PutCommand(strCmd)

# set output directory
    def setOutputDirectory(self, directory):
        strCmd = "setOutputDirectory %s\n" %(str(directory))
        self.plot.PutCommand(strCmd)

# basic slice Ax1
    def doSliceAx1(self, proc, pos, delta, ax2bin, ax3bin):
        strCmd = "doSliceAx1 %s %s %s %s %s\n" %(str(proc), str(pos), \
                  str(delta), str(ax2bin), str(ax3bin))
        self.plot.PutCommand(strCmd)

# basic slice Ax2
    def doSliceAx2(self, proc, pos, delta, ax3bin, ax1bin):
        strCmd = "doSliceAx2 %s %s %s %s %s\n" %(str(proc), str(pos), \
                  str(delta), str(ax3bin), str(ax1bin))
        self.plot.PutCommand(strCmd)

# basic slice Ax3
    def doSliceAx3(self, proc, pos, delta, ax1bin, ax2bin):
        strCmd = "doSliceAx3 %s %s %s %s %s\n" %(str(proc), str(pos), \
                  str(delta), str(ax1bin), str(ax2bin))
        self.plot.PutCommand(strCmd)

    def doFreeSlice(self, proc, org1, org2, org3, x1, x2, x3, y1, y2, y3, \
                 xbin, ybin, xrange1, xrange2, yrange1, yrange2, thickness):
        strCmd = "doFreeSlice %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s\n" %(str(proc), str(org1), \
                                                                                      str(org2), str(org3), str(x1), \
                                                                                      str(x2), str(x3), str(y1), str(y2), str(y3), \
                                                                                      str(xbin), str(ybin), str(xrange1), \
                  str(xrange2), str(yrange1), str(yrange2), str(thickness))

        self.plot.PutCommand(strCmd)

# set data [inamura 170227]-->
    def setData(self, proc, ecm, title="Empty", xkey="-", ykey="-", zkey="-"):
        from vis.UtilPlot import TempFile
        import Manyo
        TMPF = TempFile()
        temppath = TMPF.GetTempName(".srlz")
        if temppath==None:
            print "Error Error Error"
            return
        crw = Manyo.WriteSerializationFileBinary(temppath)
        crw.Save(ecm)
        del crw
        
        title = title.strip().replace(" ","_")
        strCmd = "setData %s %s %s %s %s\n"%(temppath,title,xkey,ykey,zkey)
        #print "command=",strCmd
        ret = self.plot.PutCommand(strCmd)
        if ret==None:
            return False
        else:
            return True

    def setAxesTitles(self, xtitle="X", ytitle="Y",  ztitle="Z"):
        strCmd = "setAxesTitles %s %s %s\n"%(xtitle,ytitle,ztitle)
        ret = self.plot.PutCommand(strCmd)
        if ret==None:
            return False
        else:
            return True
#<--[inamura 170227]
        
#[inamura 160920]-->
#########################################
from vis.UtilPlot import AcqPath 
def SearchPath(fname,isFile=True):
    """
    フルパスの探索
    @param  fname  　ファイル名
    @retval ファイルのフルパス
    """ 
    # カレントディレクトリを取得
    fpath = os.path.join(os.getcwd(), fname)
    # カレントディレクトリに目的ファイルがあるか
    if isFile:
        if os.path.isfile(fpath):
            # フルパスを返す
            return (os.getcwd(),fpath)
    else:
        if os.path.isdir(fpath):
            return fpath
    # 環境変数に指定されたディレクトリを取得
    ap = AcqPath()
    paths = [ap.GetSysExp(), ap.GetSysAnal(), ap.GetSysVis()]
    p_path = os.environ.get('PYTHONPATH')
    if p_path!=None:
        p_path_list = p_path.split(os.pathsep)
        for pp in p_path_list:
            paths.append(pp)
            paths.append( os.path.join(pp,"vis","SliceViewer3D") )
                
    # 全PYTHONPATHディレクトリを探索
    for path in paths:
        fpath = os.path.normpath(os.path.join(path,fname))
        # ディレクトリに目的ファイルがあるか
        if isFile:
            if os.path.isfile(fpath):
                # フルパスを返す
                return (path,fpath)
        else:
            if os.path.isdir(fpath):
                return fpath

    # 目的ファイルが見つからなかった
    return None
#########################################
view_path = SearchPath( "View", isFile=False )
if view_path!=None:
    sys.path.append(view_path)
#<--[inamura 160920]

#######################################
#  CommWx
#######################################
class CommWx(object) :
    """
    CUI から起動されるCUI-GUI 通信インターフェイスクラス
    指定されたアプリケーションをプロセスとして起動し
    プロセス間通信により、コマンドを送信し
    応答を受信する。
    コマンド・及びレスポンスは'\n'をターミネータ
    とするアスキー文字列である。
    """

    #########################################
    def NewComm(self, portno, args):
        """
        args[0] で指定されたバイソンモジュールを別プロセス
        として起動しソケットにより通信接続する。
        @param  portno ソケット通信用のポート番号
        @param  args   起動モジュール名と引数(文字列)のタプル
        @retval True: 起動と通信接続に成功　　False: 失敗
        """
        fpath = args[0]

        #[inamura 160920]-->
        target_path = SearchPath(args[0])
        if target_path==None:
            return False
        fpath=target_path[1]
        #<--[inamura 160920]

        # 起動文字列作成
        strPort = "%d" % portno
        cmd = ["python", fpath, strPort]

        # 引数文字列付加
        for arg in args:
            cmd.append(arg)

        self.plot=subprocess.Popen(cmd)

        #try:
        #    # アプリケーションを別プロセスとして起動
        #    self.plot=subprocess.Popen(cmd)
        #except:
        #    print "\nError!!  Failed to create a new process."
        #    return False

        # ソケットクライアントの準備
        sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

        # タイムアウトを 13 秒に設定
        sock.settimeout(13.0)
        # 起動待ち
        time.sleep(1.0)
        # プラットフォームが Mac なら
        if sys.platform == "darwin":
            # 起動待ち時間を追加。不具合により、Mac では一度接続に失敗すると
            # リトライによる再接続ができないため
            time.sleep(9.0)
        # 接続(リトライ10回)
        for i in range(10):
            time.sleep(3.0)
            try:
                # ローカルホストのサーバと接続
                r = sock.connect(('127.0.0.1', portno))
            except socket.error, e:
                # タイムアウト 3×10 秒
                if i == 9:
                    print '\nError: %s' % e
                    return False
            # 接続成功
            else:
                break

        # スレッド間のコマンド送受信用のキューを準備
        self.cmdQueue = Queue.Queue()
        self.ansQueue = Queue.Queue()
        # 通信サブスレッドスタート
        thin = threading.Thread(target=self.SocketClient, args=(sock,))
        thin.setDaemon(True)
        thin.start()
        # 新アプリケーション起動・接続が成功
        return True

    #############################################
    def SocketClient(self, sock) :
        """
        起動したプロセスへのコマンド送信と応答の受信
        (サブスレッドにて実行)
        @param  sock　ソケットオブジェクト
        @retval 無し
        """
        # 永久ループ
        while True:
            # 起動したアプリケーションが生きているか
            if self.plot.poll()==None:
                # キューからコマンドを読み出す
                try:
                    cmd = self.cmdQueue.get(timeout = 3.0)
                except:
                    try:
                        # 接続維持コマンド送信(3秒毎)
                        sock.send("keepalive\n")
                    except socket.error, e:
                        break
                else:
                    ans = "NACK"
                    try:
                        # アプリケーションへコマンド送信
                        sock.send(cmd)
                        # 返信読み込み
                        ans = self.GetRespons(sock)
                    except socket.error, e:
                        print '\n%s' % e

                    # 応答キューに書き込み
                    self.ansQueue.put(ans)

            # 起動したアプリケーションが終了したなら
            else:
                # アプリケーション終了による通信スレッド終了
                break
        try:
            # アプリケーション終了メッセージ
            print "\nApplication End"
            # ソケットをクローズ
            sock.close()
        except:
            # Python インタープリタ終了時に例外が発生することがあるため
            pass

    #############################################
    def GetRespons(self, sock) :
        """
        レスポンスを取得
        @param  sock ソケットオブジェクト
        @retval 無し
        """
        # レスポンスを受信するまで待っている
        while True:
            ans = ""
            # レスポンスが分割された場合のプロテクト
            for i in range(10):
                # レスポンス読み込み
                try:
                    data = sock.recv(1024)
                except:
                    return "NACK"
                # 改行コードがあるか
                if '\n' in data:
                    # 改行コードの前までをコマンドとして取り出す
                    ans = ans + data[:data.find('\n')]
                    # 受信したレスポンスを返す
                    return ans

                # レスポンスが未完であれば
                elif len(data) > 0:
                    # 連結
                    ans = ans + data
                    time.sleep(0.1)


    #########################################
    def IsAliveComm(self):
        """
        アプリケーションの生存確認
        @param  無し
        @retval True: 生存、False: 消滅
        """
        # アプリケーションが生きているか
        if self.plot.poll()==None:
            return True
        # 既に閉じられている
        else:
            return False

    #########################################
    def PutCommand(self, strCmd):
        """
        コマンドを通信スレッドへ送信(キューに書き込む)
        (本関数はメインスレッドで実行)
        @param  strCmd 通信コマンド文字列
        @retval コマンドに対する応答
        """
        # アプリケーションが生きているか
        if self.plot.poll()==None:
            # 送信キューへ書き込み
            self.cmdQueue.put(strCmd)

            # アプリケーションからの返信待ち(タイムアウト15秒)
            try:
                # キューからコマンドを読み出す
                answer = self.ansQueue.get(timeout = 15.0)
            except:
                # 15秒以内に応答が無かったので
                answer = "NACK"
            return answer
        # 既に閉じられている
        else:
            # 終了エラー
            print "The aplication has been already closed."
            return None #[inamura 170227]


if __name__ == '__main__':
    SliceViewer()
