#!/usr/bin/python
# -*- coding: utf-8 -*-
"""
WxApp.py
GUI ローンチャー
wxPython アプリとCUI のインターフェイス
"""
import subprocess
import tempfile
import time
import cPickle as pickle
import os, sys
import wx
import threading, Queue
import socket
from vis.UtilPlot import AcqPath


#######################################
#  WxApp
#######################################  
class WxApp(wx.App):
    """
    wxPython アプリとCUI のインターフェイスクラス
    GUI アプリケーションから起動され、最上位のWxPython 
    アプリケーション及びソケットサーバとして機能する。
    """
    #############################################
    def OnStart(self, portno, closeFlag, func):
        """
        タイマー監視のスタート
        @param  portno     ソケットのポート番号
        @param  closeFlag  親プロセス終了時に自死するかどうかのフラグ
        @param  func       コマンド解析・実行用のコールバック関数
        @retval 無し
        """ 
        self.func = func
        self.closeFlag = closeFlag
        
        # ソケットサーバー準備
        sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        sock.bind(('127.0.0.1', portno))
        # タイムアウトを 15 秒に設定
        sock.settimeout(15.0)
        # 受信準備
        sock.listen(1)
        # クライアントと接続
        try:
            (conn, sa) = sock.accept()
        except socket.error, e:
            print 'Error: %s' % e
            return
        # 受信待ち時停止指定
        conn.setblocking(True)
        
        # スレッド間通信用のキューを準備
        self.datQueue = Queue.Queue()
        self.ansQueue = Queue.Queue()
        
        # サブスレッドの終了フラグを準備
        self.stopFlag = False

        # サブスレッドスタート
        th = threading.Thread(target=self.SocketServer, args=(conn,))
        th.setDaemon(True)
        th.start()

        # 0.3　秒毎のタイマー
        self.timer = wx.Timer(self)
        self.timer.Start(300)
        self.Bind(wx.EVT_TIMER, self.TimerHandler)

    #############################################
    def TimerHandler(self, evt):
        """
        タイマー監視ルーチン
        CUIからのコマンドを受信したらコマンド解析クラスを
        通じてデータを送信
        @param  evt イベント情報
        @retval 無し
        """ 
        # コマンドがあれば
        if self.datQueue.qsize() > 0:
            # キューからコマンドを読み出す
            strCmd = self.datQueue.get()
            # コマンド実行前の時間を保存
            t1 = time.time()
            # GUI アプリから渡されたコマンド解析処理コールバック関数を実行
            ret = apply(self.func, (strCmd,))  

            # コマンド終了後の時間を取得
            t2 = time.time()
            # コマンドの実行に 10秒以上かかった場合は
            # 通信スレッドが既に NACK を送信しているため、ここでは送信しない 
            if t1+10.0 > t2:
                # 応答が特に無ければ正常応答を返信
                if ret == None:
                    ret = "ACK\n"
                # 返信要求をだす
                self.ansQueue.put(ret)

    #####################################################    
    def OnGUIClose(self):
        """
        トップレベルウィンドウクローズイベント対応処理
        @param  無し   
        @retval 無し
        """      
        # タイマー停止 
        self.timer.Stop()
        # サブスレッドの停止フラグを立てる
        self.stopFlag = True

    #############################################        
    def SocketServer(self, sock) :
        """
        ソケットからコマンドを取得し、レスポンスを返す
        (サブスレッドにて実行)
        @param  sock ソケットオブジェクト
        @retval 無し
        """ 
        # 永久ループ(トップレベルのウィンドウクローズ時、またはクライアント終了時に終了)
        while True:
            # アプリケーションが終了したか
            if self.stopFlag:
                # 接続終了
                break
            # コマンドを読み込む
            cmd = self.GetCommand(sock)
            # 接続維持コマンドではないならば
            if not cmd == "keepalive":
                # コマンドをメインスレッドへ送信
                self.datQueue.put(cmd)
                # メインスレッドからの返信待ち(タイムアウト10秒)
                try:
                    # キューから応答を読み出す
                    answer = self.ansQueue.get(timeout = 10.0)
                except:
                    # タイムアウトでコマンド実行エラーを返信
                    answer = "NACK\n"
                try:
                    # CUIへ応答を返信
                    sock.send(answer)
                except:
                    # クライアントが接続を終了している
                    break
                
        # 接続終了   
        sock.close()
        # wxPython のメインループを終了させる(Linux では必要)
        wx.Exit()


    #############################################        
    def GetCommand(self, sock) :
        """
        1個のコマンドを取得
        @param  sock ソケットオブジェクト
        @retval 無し
        """         
        # コマンドを受信するまで待っている
        while True:
            cmd = ""
            # コマンドが分割された場合のプロテクト
            # 1個のコマンドが10以上に分割されていればコマンドを破棄
            for i in range(10):
                try:
                    # コマンド読み込み
                    data = sock.recv(1024)
                    # クライアントが終了したか
                    # (Linux Python 2.4　対応: 例外が発生せずに、ブロッキングされなくなるようだ)
                    if data == "":
                        # 親プロセス終了時の終了が指定されているか
                        if self.closeFlag:
                            self.stopFlag = True
                            # アプリケーション終了要求を出す
                            return "close"
                    # 改行コードがあるか
                    if '\n' in data:
                        # 改行コードの前までをコマンドとして取り出す
                        cmd = cmd + data[:data.find('\n')]
                        # コマンドを返す
                        return cmd
                                        
                    # コマンドが未完であれば
                    elif len(data) > 0:
                        # 連結
                        cmd = cmd + data
                        time.sleep(0.1)
                # クライアント終了による例外発生      
                except:
                    # 親プロセス終了時の終了が指定されているか
                    if self.closeFlag:
                        # クライアント終了による、アプリケーション終了
                        self.stopFlag = True
                        return "close"

#######################################
#  CommWx
####################################### 
class CommWx(object) :
    """
    CUI から起動されるCUI-GUI 通信インターフェイスクラス
    指定されたアプリケーションをプロセスとして起動し
    プロセス間通信により、コマンドを送信し
    応答を受信する。
    コマンド・及びレスポンスは'\n'をターミネータ
    とするアスキー文字列である。 
    """
    
    #########################################
    def NewComm(self, portno, args):
        """
        args[0] で指定されたバイソンモジュールを別プロセス
        として起動しソケットにより通信接続する。
        @param  portno ソケット通信用のポート番号
        @param  args   起動モジュール名と引数(文字列)のタプル
        @retval True: 起動と通信接続に成功　　False: 失敗
        """ 

        wxapppath = ""
        # 環境変数PYTHONPATHに指定されたディレクトリを取得
        paths = str( os.environ.get('PYTHONPATH')).split(os.pathsep)
        ##[inamura 100720]-->
        paths_b = []
        for pp in paths:
            paths_b.append( os.path.join( pp,"vis" ) )
        paths = paths+paths_b
        ##<--[inamura 100720]
        # 全PYTHONPATHディレクトリを探索
        for path in paths:
            fpath = os.path.normpath(os.path.join(path,args[0]))
            # ディレクトリに目的ファイルがあるか
            if os.path.isfile(fpath):
                wxapppath = fpath
                break
        # 指定されたモジュールが見つからなかったら
        if wxapppath == "":
            fpath = AcqPath().GetModulePath(args[0])
            if fpath == None:
                print "\nError!!  There is not %s in any directory specified by PYTHONPATH." % args[0]
                return False
                
        # 起動文字列作成
        strPort = "%d" % portno
        cmd = ["python", fpath, strPort]
        # 引数文字列付加       
        for arg in args:
            cmd.append(arg) 

        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." 
          


