import os
import sys
from stat import *
import requests
import zipfile
from tqdm import tqdm
from tkinter import *
from tkinter import ttk
import tkinter as tk
import tkinter.ttk as ttk
from tkinter import messagebox
import re
import threading
import datetime
import time
import subprocess
from send2trash import send2trash
from ownFunction.unitconv1000 import unitconv1000

#コメントに「◀◀◀◀◀◀◀」が含まれる行は、目的のファイル・アプリ・フォルダなどに応じて変更することにより、このツールを汎用のものとする
targetFileName = 'TimeStamper__B.0.6_Mac.zip'                                               #◀◀◀◀◀◀◀ダウンロードするファイル
targetURL = 'https://mac-in.net/appli/time_stamper/archive/' + targetFileName               #◀◀◀◀◀◀◀ダウンロードするファイルの完全URL
targetDirectory = '../time_stamper/archive/'                                                #◀◀◀◀◀◀◀ツール(ダウンロード&インストール)の位置から見た目的のファイルまでの階層
webToolURL = 'https://appli.mac-in.net/filesize_getter/filesize_getter-here_server.php'     #ツール(ダウンロード&インストール)
webToolparameter = '?filepath='                                                             #パラメーターのキー
requestURL = webToolURL + webToolparameter + targetDirectory + targetFileName               #パラメーターを付けたツールのURL
unZipFolder = 'タイムスタンパーver.β.0.6-20210328'                                             #◀◀◀◀◀◀◀解凍して得られるフォルダ
appName = 'タイムスタンパーver.β.0.6.app'                                                      #◀◀◀◀◀◀◀解凍して得られるアプリ
reaadmeText = 'お読み下さい.txt'                                                              #◀◀◀◀◀◀◀解凍して得られる文書
reaadmeFullPATH = os.path.dirname(__file__) + '/' + unZipFolder + '/' + reaadmeText         #文書の絶対パス
runtimePATH = '/' + appName + '/Contents/MacOS/Runtime'                                     #◀◀◀◀◀◀◀アプリパッケージ内のランタイム
#downloadFolder = os.getenv('HOME') + '/Downloads/'                                          #Macの「ダウンロード」フォルダに格納する場合
#downloadFolder = os.path.dirname(__file__) + '/'                                            #この実行ファイル(Runtime)と同じフォルダに格納する場合
downloadFolder = os.path.dirname(__file__) 
if '/Contents/Resources' in downloadFolder:
    LiPath = sys.executable.split('/')
    downloadFolder = '/'.join(LiPath[:-4]) + '/'
else:
    downloadFolder += '/'
#downloadFolder.replace('/Contents/Resources', '') + '/'          #このアプリ(パッケージ)と同じフォルダに格納する場合
print(downloadFolder)

downloadFilePath = downloadFolder + targetFileName
unZipFolderPath = downloadFolder + unZipFolder
progressWindowTitle = 'インストーラー ver.β-0.1'      #進捗ウインドウのタイトル
progressWindowSize = "600x300"      #GUIのウインドウのサイズ
progressBarLength = 200             #GUIのウインドウ上のプログレスバーの長さ
fileSize = 0                #ダウンロード対象ファイルの容量
downloadTotal = 0           #GUIのダウンロード済み容量／ThreadProgress
coontentsNum = 0            #ZIPの中身の数
contentsCount = 0           #ZIPの回答された数をカウント
progressRate = 1000         #プログレス表示のリフレッシュレート／単位ミリ秒

disabledColor='grey70'
enableColor='black'
class Color:
    BLACK     = '\033[30m'
    RED       = '\033[31m'
    GREEN     = '\033[32m'
    YELLOW    = '\033[33m'
    BLUE      = '\033[34m'
    PURPLE    = '\033[35m'
    CYAN      = '\033[36m'
    WHITE     = '\033[37m'
    END       = '\033[0m'
    BOLD      = '\038[1m'
    UNDERLINE = '\033[4m'
    INVISIBLE = '\033[08m'
    REVERCE   = '\033[07m'

def closeProcess():
    print(Color.BLUE + '関数 closeProcess()に入りました' + Color.END)  ###デバッグ
    sys.exit()

def dialogWarning(dTitle, dMessage):
    print(Color.BLUE + '関数 dialogWarning()に入りました' + Color.END)  ###デバッグ
    global root
    root = tk.Tk()
    root.withdraw()
    ret = messagebox.showwarning(dTitle, dMessage)
    #closeProcess()
    sys.exit(1)

def getNumber(iString):
    noSpace = iString.replace(' ', '')
    reSearch = re.search('File-Size:', noSpace, flags=re.IGNORECASE)
    endString = noSpace[reSearch.end():]
    nnumberList = re.findall('^[0-9]+', endString)
    getNumber = int(nnumberList[0])
    return(getNumber)

def downloadTask():
    print(Color.BLUE + '関数「' + sys._getframe().f_code.co_name + '」に入りました。(Threading)' + Color.END)     #デバッグ
    startDT = datetime.datetime.now()
    startDTS = startDT.strftime('%Y/%m/%d %a %H:%M:%S')
    print(Color.BLUE + 'ダウンロードを開始します。少々お待ち下さい。開始時刻: ' + startDTS + Color.END)
    global downloadTotal
    global fileSize
    global labelDownloadTotal
    global labelFileSize
    labelFileSize.configure(text='総量 ' + unitconv1000(str(fileSize), '0.1') + 'Byte', foreground=enableColor)
    res = requests.get(targetURL, stream=True)
    pbar = tqdm(total=fileSize, unit="B", unit_scale=True)
    with open(downloadFilePath , 'wb') as file:
        for chunk in res.iter_content(chunk_size=1024):
            file.write(chunk)
            pbar.update(len(chunk))     #コンソールのプログレスバーを進める
            downloadTotal += len(chunk)         #GUIのプログレスバーを進める
            labelDownloadTotal.configure(text=unitconv1000(str(downloadTotal), '0.1') + 'Byte', foreground=enableColor)
        pbar.close()
    endDT = datetime.datetime.now()
    endDTS = endDT.strftime('%Y/%m/%d %a %H:%M:%S')
    print(Color.BLUE + 'ダウンロードが完了しました。完了時刻: ' + endDTS + '結果: ' + str(downloadTotal) + ' Byte' + Color.END)

def unzipTask():
    global coontentsNum                             #中身の数の入れ物
    global contentsCount
    global labelContentsCount
    global labelCoontentsNum
    print(Color.BLUE + '関数「' + sys._getframe().f_code.co_name + '」に入りました。' + Color.END)     #デバッグ
    print(Color.BLUE + '解凍を始めます。（Thread）' + Color.END)
    print('downloadFilePath: ' + downloadFilePath)
    try:
        with zipfile.ZipFile(downloadFilePath) as existing_zip:
            coontentsList = existing_zip.namelist()
            coontentsNum = len(coontentsList)               #中身の数を入れる
            coontentsNumStr = '{:,}'.format(coontentsNum)
            print(Color.BLUE + '中身は ' + coontentsNumStr + '件あります。' + Color.END)
            labelCoontentsNum.configure(text='総数 ' + coontentsNumStr + ' 件', foreground=enableColor)
            contentsCount = 0
            for info in existing_zip.infolist():
                contentsCount += 1
                info.filename = info.filename.encode('cp437').decode('utf-8')
                existing_zip.extract(info, path=downloadFolder)
                contentsCountStr = '{:,}'.format(contentsCount)
                labelContentsCount.configure(text=contentsCountStr + ' 件', foreground=enableColor)
    except Exception as err:
        errorMessage(err, sys._getframe().f_code.co_name + '() > 3')
        sys.exit(1)

def permissionTask():
    print(Color.BLUE + '関数「' + sys._getframe().f_code.co_name + '」に入りました。' + Color.END)     #デバッグ
    print(Color.BLUE + 'パーミッションを変更します。' + Color.END)
    global runtimeFullPATH
    global labelPermission
    perm = 0o755
    os.chmod(runtimeFullPATH, perm)
    mode = os.stat(runtimeFullPATH).st_mode
    if S_IMODE(mode) == perm:
        labelPermission.configure(text="変更済み", foreground=enableColor)
    else:
        dialogWarning('エラー', 'パーミッションの変更ができませんでした。')
        sys.exit(1)


def trashboxTask():
    global labelTrashbox
    print(Color.BLUE + '関数「' + sys._getframe().f_code.co_name + '」に入りました。' + Color.END)     #デバッグ
    print('不要になったファイルをゴミ箱に移動します。')
    send2trash(downloadFilePath)
    thisFilePATH = os.path.dirname(os.path.abspath(__file__)) + '/' + os.path.basename(__file__)
    if '/Contents/Resources' in thisFilePATH:
        LiPath = sys.executable.split('/')
        thisFilePATH = '/'.join(LiPath[:-3]) + '/'
    else:
        thisFilePATH += '/'
        
    send2trash(thisFilePATH)        #開発中は止めておく／この実行ファイル自身をゴミ箱に移動する
    labelTrashbox.configure(text="ゴミ箱へ移動済み", foreground=enableColor)

def progressDownload():
    #print(Color.BLUE + '関数「' + sys._getframe().f_code.co_name + '」に入りました。' + Color.END)     #デバッグ
    global fileSize
    global downloadTotal
    global root
    global ThreadDownload
    global ThreadUnzip
    #root.labelDownloadTotal.configure(text=unitconv1000(str(contentsCount), '0.1'))
    #print('downloadTotal >= fileSize：' + str(downloadTotal) + '>=' + str(fileSize))         #デバッグ
    if downloadTotal >= fileSize:
        print(Color.BLUE + '予め取得したファイルサイズと、ダウンロードされた容量が一致しました。(あるいは超えた)' + Color.END)
        ThreadDownload.join()
        ThreadUnzip = threading.Thread(target=unzipTask)    #解凍をスレッドで
        ThreadUnzip.daemon = True    #スレッドをデーモン化することによってメインスレッドが終了すると自動的に終了するようにする
        ThreadUnzip.start()              #解凍のスレッド開始
        root.after(progressRate, progressUnzip)           #プログレスを解凍タスクに切り替える
    else:
        root.after(progressRate, progressDownload)     #nミリ秒ごとにGUIウインドウを更新
        
def progressUnzip():
    #print(Color.BLUE + '関数「' + sys._getframe().f_code.co_name + '」に入りました。' + Color.END)     #デバッグ
    global runtimeFullPATH
    global coontentsNum
    global contentsCount
    global root
    global ThreadUnzip
    global ThreadPermission
    #root.labelUnzipTotal.configure(text=unitconv1000(str(downloadTotal), '0.1'))
    if contentsCount >= coontentsNum:
        print(Color.BLUE + '予め取得した項目数と、解凍して得られた項目数が一致しました。(あるいは超えた)' + Color.END)
        ThreadUnzip.join()
        runtimeFullPATH = downloadFolder + unZipFolder + runtimePATH
        print('downloadFolder：' + downloadFolder)                      #デバッグ
        print('unZipFolder：' + unZipFolder)                            #デバッグ
        print('runtimePATH：' + runtimePATH)                            #デバッグ
        print(Color.BLUE + runtimeFullPATH + ' の存在を確認します。' + Color.END)
        notFoundS = 10                              #タイムアウトの秒数
        countS = 0.1                                #ファイルの存在確認をする間隔
        while not os.path.isfile(runtimeFullPATH):
            time.sleep(countS)
            notFoundS -= countS
            if notFoundS < 0:
                print(Color.RED + 'タイムアウトしました。原因：解凍されて得られるはずのRuntimeの存在が確認できませんでした。従って、以降の処理が継続できません。' + Color.END)
                sys.exit(1)
        print(Color.BLUE + 'runtimeの存在を確認できました。' + Color.END)
        #ThreadDownload.join()
        ThreadPermission = threading.Thread(target=permissionTask)    #パーミッション変更をスレッドで
        ThreadPermission.daemon = True    #スレッドをデーモン化することによってメインスレッドが終了すると自動的に終了するようにする
        ThreadPermission.start()              #解凍のスレッド開始
        root.after(progressRate, progressPermission)           #プログレスを解凍タスクに切り替える
    else:
        root.after(progressRate, progressUnzip)     #nミリ秒ごとにGUIウインドウを更新

def progressPermission():
    print(Color.BLUE + '関数「' + sys._getframe().f_code.co_name + '」に入りました。' + Color.END)     #デバッグ
    global ThreadPermission
    global ThreadTrashbox
    ThreadPermission.join()
    ThreadTrashbox = threading.Thread(target=trashboxTask)    #不要になったファイルをゴミ箱に移動をスレッドで
    ThreadTrashbox.daemon = True    #スレッドをデーモン化することによってメインスレッドが終了すると自動的に終了するようにする
    ThreadTrashbox.start()              #不要になったファイルをゴミ箱に移動の開始
    root.after(progressRate, progressTrashbox)           #プログレスを解凍タスクに切り替える

def progressTrashbox():
    global ThreadTrashbox
    print(Color.BLUE + '関数「' + sys._getframe().f_code.co_name + '」に入りました。' + Color.END)     #デバッグ
    ThreadTrashbox.join()
    endProcess()

def endProcess():
    global root
    global endButton
    print('reaadmeFullPATH：' + reaadmeFullPATH)                    #デバッグ
    subprocess.call(["open", "-R", reaadmeFullPATH])
    endButton.configure(state=NORMAL, command=sys.exit)
    root.mainloop

def progressWindowSet():
    print(Color.BLUE + '関数「' + sys._getframe().f_code.co_name + '」に入りました。' + Color.END)     #デバッグ
    try:                            #デバッグ
        global root
        global labelDownloadTotal
        global labelFileSize
        global endButton
        global labelContentsCount
        global labelCoontentsNum
        global labelPermission
        global labelTrashbox
        widthLeft = 11
        widthRight = 13
        widthOne = 25

        root = tk.Tk()
        root.title(u'' + progressWindowTitle)
        #root.geometry("750x300")
        frame00 = tk.LabelFrame(root, bd=0, padx=5, pady=5)

        frameT =tk.LabelFrame(frame00, width=30, text='ターゲット')
        labelT1 = ttk.Label(frameT, width=1, text="").grid(row=0,column=0)
        labelPermission = ttk.Label(frameT, width=widthOne, anchor='center', text=appName, foreground=enableColor).grid(row=0,column=1)
        labelT3 = ttk.Label(frameT, width=1, text="").grid(row=0,column=2)

        frame1 = tk.LabelFrame(frame00, width=30, text='ダウンロード')
        #label11 = ttk.Label(frame1,text="ダウンロード：").grid(row=0,column=0)
        labelDownloadTotal = ttk.Label(frame1, width=widthLeft, anchor='e', text='0 Byte', foreground=disabledColor)
        labelDownloadTotal.grid(row=0,column=0)
        label13 = ttk.Label(frame1, width=3,text=" / ").grid(row=0,column=1)
        labelFileSize = ttk.Label(frame1, width=widthRight, anchor='e', text='総量 0 Byte', foreground=disabledColor)
        labelFileSize.grid(row=0,column=2)

        frame2 = tk.LabelFrame(frame00, bd=0)
        label21 = ttk.Label(frame2,text='↓').grid(row=0,column=0)

        frame3 = tk.LabelFrame(frame00, width=30, text="解凍")
        labelContentsCount = ttk.Label(frame3, width=widthLeft, anchor='e', text="0 件", foreground=disabledColor)
        labelContentsCount.grid(row=0,column=0)
        label32 = ttk.Label(frame3, width=3,text=" / ").grid(row=0,column=1)
        labelCoontentsNum = ttk.Label(frame3, width=widthRight, anchor='e', text='総数 0 件', foreground=disabledColor)
        labelCoontentsNum.grid(row=0,column=2)

        frame4 = tk.LabelFrame(frame00, bd=0)
        label41 = ttk.Label(frame4,text='↓').grid(row=0,column=0)

        frame5 = tk.LabelFrame(frame00, width=30, text="パーミッション")
        label51 = ttk.Label(frame5, width=1, text="").grid(row=0,column=0)
        labelPermission = ttk.Label(frame5, width=widthOne, anchor='center', text="変更", foreground=disabledColor)
        labelPermission.grid(row=0,column=1)
        label53 = ttk.Label(frame5, width=1, text="").grid(row=0,column=2)

        frame6 = tk.LabelFrame(frame00, bd=0)
        label61 = ttk.Label(frame6,text='↓').grid(row=0,column=0)

        frame7 = tk.LabelFrame(frame00, width=30, text="不要ファイル")
        label71 = ttk.Label(frame7, width=1, text="").grid(row=0,column=0)
        labelTrashbox = ttk.Label(frame7, width=widthOne, anchor='center', text="ゴミ箱へ移動", foreground=disabledColor)
        labelTrashbox.grid(row=0,column=1)
        label73 = ttk.Label(frame7, width=1, text="").grid(row=0,column=2)

        frame8 = tk.LabelFrame(frame00, bd=0)
        label81 = ttk.Label(frame8,text='↓').grid(row=0,column=0)

        endButton = tk.Button(frame00, text="終了", state=DISABLED, disabledforeground=disabledColor)
        endButton.grid(row=99,column=0)

        frame00.grid(row=0, column=0)
        frame8.grid(row=8, column=0)
        frame7.grid(row=7, column=0)
        frame6.grid(row=6, column=0)
        frame5.grid(row=5, column=0)
        frame4.grid(row=4, column=0)
        frame3.grid(row=3, column=0)
        frame2.grid(row=2, column=0)
        frame1.grid(row=1, column=0)
        frameT.grid(row=0, column=0)
    except Exception as err:                                        #デバッグ
        errorMessage(err, sys._getframe().f_code.co_name + '()')    #デバッグ
        sys.exit(1)                                                  #デバッグ

def startRoutine():
    #print(Color.BLUE + '関数「' + sys._getframe().f_code.co_name + '」に入りました。' + Color.END)
    global fileSize
    global root
    global ThreadDownload
    progressWindowSet()

    ThreadDownload = threading.Thread(target=downloadTask)    #ダウンロードをスレッドで
    ThreadDownload.daemon = True    #スレッドをデーモン化することによってメインスレッドが終了すると自動的に終了するようにする
    ThreadDownload.start()              #ダウンロードのスレッド開始
    root.after(progressRate, progressDownload)     #nミリ秒ごとにGUIウインドウを更新
    root.mainloop()


def errorMessage(err, positionStr):
    print(Color.RED + '予期せぬエラー' + Color.END + ' in ' + positionStr)
    print(err)
    exc_type, exc_obj, exc_tb = sys.exc_info()
    fname = os.path.split(exc_tb.tb_frame.f_code.co_filename)[1]
    print(exc_type, 'Line: ' + str(exc_tb.tb_lineno), ' in File: ' + fname)

def main():
    print(Color.BLUE + '関数「' + sys._getframe().f_code.co_name + '」に入りました。' + Color.END)     #デバッグ
    global fileSize
    try:
        #with int(requests.head(targetURL).headers["content-length"]) as fileSize:     #ヘッダーにKey「content-length」がある場合
        with requests.get(requestURL, stream=True, timeout=30) as returnValue:      #PHPで用意したwebツールに問い合わせる
            fileSize = getNumber(returnValue.text)
            print(Color.BLUE + 'ファイルサイズの取得に成功しました！　' + str(fileSize) + ' Byte' + Color.END)
    except Exception as err:
        errorMessage(err, sys._getframe().f_code.co_name + '() > 1')
        sys.exit(1)

    if not os.path.exists(unZipFolderPath):
        try:
            with open(downloadFilePath, mode='x') as f:
                startRoutine()
        except FileExistsError:
            if os.path.getsize(downloadFilePath) == fileSize:
                messageString = 'ダウンロードしたファイルが既にあります。既存のファイル「' + downloadFilePath + '」の名称を変更してからもう一度実行してください。'
                print(Color.YELLOW + messageString + Color.END)
                dialogWarning('警告', messageString)
            else:
                messageString = 'ダウンロードしたファイルが既にありますがサイズが違うのでダウンロードに失敗していると推測されます。既存のファイル「' + downloadFilePath + '」を削除してからもう一度実行してください。'
                print(Color.YELLOW + messageString + Color.END)
                dialogWarning('警告', messageString)
            sys.exit(1)
        except Exception as err:
            errorMessage(err, sys._getframe().f_code.co_name + '() > 2')
            os.remove(downloadFilePath)
            sys.exit(1)
    else:
        messageString = '解凍したフォルダが既にあります。既存のフォルダ「' + unZipFolderPath + '」の名称を変更してからもう一度実行してください。'
        print(Color.YELLOW + messageString + Color.END)
        dialogWarning('警告', messageString)
    closeProcess()

if __name__ == "__main__":
    main()