import os
import re
import math
import time
#import rarfile
import zipfile
#import shutil
#import difflib
import subprocess
from pathlib import Path

m_dbgLevel = 0
listDbgStr:list[str] = []

#
def IsEmptyStr(string:str) -> bool:
    if type(string) is not str:
        return True
    
    return 0 == len(string.strip())

#
def GetCurrentTime() -> str:
    # 현재 시간을 구하고 구조체로 변환
    current_time_struct = time.localtime()

    # 구조체에서 연, 월, 일, 시간, 분, 초를 추출
    year = current_time_struct.tm_year
    month = current_time_struct.tm_mon
    day = current_time_struct.tm_mday
    hour = current_time_struct.tm_hour
    minute = current_time_struct.tm_min
    second = current_time_struct.tm_sec
    
    strRet = (f"{year}/{month}/{day}_{hour}:{minute}:{second}")

    return strRet 

#for debug
def DbgOut(strInput:str, bPrint:bool = False):
    strMsg = (f"{GetCurrentTime()} : {strInput}")
    listDbgStr.append(strMsg)
    
    if True == bPrint:
        print(strMsg)
    
def printDbgMessages():
    for line in listDbgStr:
        print(line)

# 입력된 패스에서 부모 폴더를 찾는다. 0 은 자기자신 1은 부모, 숫자대로 위로.
def GetParentDirName(FullPath:str, nUp:int) -> str:
    parts = FullPath.split(os.sep)
    
    nTrgIdx = 0
    if nUp < len(parts):
        nTrgIdx = len(parts) -nUp -1
    elif nUp < 0:
        nTrgIdx = len(parts) - 1
    else:
        nTrgIdx = 0
        
    return parts[nTrgIdx]
        
# os 모듈을 사용하여 자식 폴더를 반환
def GetSubDirectories(folder_path:str)-> list[str]:
    subdirectories = [
        d for d in os.listdir(folder_path) 
            if os.path.isdir(os.path.join(folder_path, d))]

    return subdirectories

# 입력된 경로의 자식 폴더를 찾아 반환한다.
# 반환하는 리스트는 리커시브 - 손자, 증손자 폴더까지 전부 포함한다
def GetAllSubDirectories(root_dir:str)-> list[str]:
    subdirectories:list[str] = []

    # root_dir에서 하위 디렉토리 및 파일 목록을 얻음
    for dirpath, dirnames, filenames in os.walk(root_dir):
        # 하위 디렉토리 목록을 반복하며 하위 디렉토리만 추출
        for dirname in dirnames:
            path = os.path.join(dirpath, dirname)
            
            if True == IsFinalFolder(path):
                subdirectories.append(path)

    return subdirectories

def ListFileExtRcr(pathTrg:str, strExt:str) -> list[str]:
    listRet:list[str] = []

    # pathTrg의 하위 디렉토리 및 파일 목록을 얻음
    for dirpath, dirnames, filenames in os.walk(pathTrg):
        for file in filenames:
            extTmp = GetExtStr(file, False)
            if extTmp.lower() == strExt and file.startswith('.'):
                listRet.append(os.path.join(dirpath, file))

    return listRet

# 입력된 경로가 자식 폴더를 가지고 있는지 판단한다.- 최종 폴더인지 여부 
# 자식이 없으면 True, 자식이 있으면 False
def IsFinalFolder(path:str) -> bool:
    bRet = True
    
    contents = os.listdir(path)
    for item in contents:
        if True == os.path.isdir(item):
            bRet = False
            break
    
    return bRet;

# 어떤 경로 안에서 특정 확장자의 파일을 뽑아내어 그 리스트를 반환한다.
def FindFileFromExt(path:str, ext:str) -> list[str]:
    bDot = False
    if 0 <= ext.find('.'):
        bDot = True
    
    listRet:list[str] = []
    if False == os.path.exists(path):
        return listRet
    
    contents = os.listdir(path)
    for item in contents:
        if True == os.path.isdir(item):
            continue
                
        extItem = GetExtStr(item, bDot)
        if extItem.lower() == ext.lower():
            listRet.append(item)
        
    return listRet

       
# 파일 이름에서 확장자를 뽑아낸다. True : '.' 을 포함한다.
def GetExtStr(file_path:str, bDot:bool = True) -> str:
    retStr = ""
    # 파일 경로에서 마지막 점을 찾아 확장자를 추출
    last_dot_index = file_path.rfind('.')
    if last_dot_index == -1:
        retStr = ""  # 점이 없는 경우 확장자가 없음
    else:
        if True == bDot:
            retStr = file_path[last_dot_index:]
        else:
            retStr = file_path[last_dot_index+1:]
        
    return retStr


# 문자열에 포함된 단어를 지운다.
def RmvSubString(mainString:str, subString:str) -> str:
    # 문자열에서 부분 문자열의 인덱스를 찾습니다.
    strIdx = mainString.find(subString)
    if strIdx == -1:  # 부분 문자열이 존재하지 않으면 그대로 반환합니다.
        return mainString
    
    endIdx = strIdx + len(subString)

    # 부분 문자열을 제거하고 새로운 문자열을 반환합니다.
    return mainString[:strIdx] + mainString[endIdx:]

def ExtractZIP(zip_file:str, extract_to:str):
    with zipfile.ZipFile(zip_file, 'r') as zf:
        zf.extractall(extract_to)

#
def CreateZIP(output_zip:str, *files):
    with zipfile.ZipFile(output_zip, 'w') as zf:
        for file in files:
            pathTemp = os.path.join('root', os.path.basename(file))
            zf.write(file, pathTemp)
    
    bRet = False
    if os.path.exists(output_zip):
        bRet = True
    
    return bRet

# 파일 리스트에 들어있는 파일만 골라서 압축을 합니다. 상대경로를 제거하는게 기본값.
def CreateZIPShell(zipName:str, *files, bRmvRPath:bool = True)-> bool:
    command = "zip "
    
    if True == bRmvRPath:
        command += "-j "
    
    command += f"\"{zipName}\" "
    
    # 이중 리스트인 이유를 모르겠다.
    for file in files:
        strTemp = ""
        if isinstance(file, list):
            strTemp = ' '.join(file)
        else:
            strTemp = f"\"{file}\" "
        
        command += strTemp
            
    #    for item in file:
    #        command += f"\"{item}\" "
    result = subprocess.run(command, shell=True, 
                            capture_output=True, text=True)
    
    bRet = False
    if 0 == result.returncode:
        bRet = True
    
    return bRet

# 특정 확장자만 쉘을 이용해서 압축한다
def CreateZIPShExt(zipName:str, TrgExt:str):
    command = f"zip -j {zipName} *.{TrgExt}"
    
    result = subprocess.run(command, shell=True, capture_output=True, text=True)
    
    bRet = False
    if 0 == result.returncode:
        bRet = True
    
    return bRet
    
# JSON 을 트리 구조로 출력한다.
def PrintJSONTree(data, indent:int=0):
    if isinstance(data, dict):
        for key, value in data.items():
            print('  ' * indent + str(key))
            PrintJSONTree(value, indent + 1)
    elif isinstance(data, list):
        for item in data:
            PrintJSONTree(item, indent)
    else:
        print('  ' * indent + str(data))

# 세로 크기를 가로 비율대로 줄여서 반환 (가로를 기준으로 세로 길이)
def GetRSzHeight(nWidth:int, nHeight:int, nTrgW:int) -> int:
    if nWidth <= 0 or nTrgW <= 0:
        return 0;

    fRatio = nTrgW / nWidth
    nTrgH = round(nHeight * fRatio)
    
    return nTrgH

# 가로 크기를 세로 비율대로 줄여서 반환 (세로를 기준으로 가로 길이)
def GetRSzWidth(nWidth:int, nHeight:int, nTrgH:int) -> int:
    if nHeight <= 0 or nTrgH <= 0:
        return 0;
    
    fRatio = nTrgH / nHeight
    nTrgW = round(nWidth * fRatio)
    
    return nTrgW

# 엑셀 열 너비는 약 7.5픽셀 단위
# 엑셀 행 높이는 약 25픽셀 단위
# 이대로는 사이즈가 안 맞아서 적당히 곱해줌
def GetCellSize(nImgW:int, nImgH:int, DPI:int = 96) -> tuple[float, float]:
    column_width = (nImgW / DPI) * 7.5 * 2
    row_height = (nImgH / DPI) * 25 * 3
    
    return column_width, row_height

def GetSheetInfoValue(text:str) -> tuple[str, list[str]]:
    if IsEmptyStr(text):
        return "", []
    
    pattern = r'<(.*?)>'
    matches = re.findall(pattern, text)
    cleaned = re.sub(pattern, '', text)
    
    return cleaned, matches

# 두 GPS 좌표 간 거리 구하기 (미터)
def GetDistanceGPS(lat1:float, lon1:float, lat2:float, lon2:float) -> float:
    # 지구의 반지름 (단위: 미터)
    R = 6371008.8 

    # 위도와 경도를 라디안으로 변환
    lat1 = math.radians(lat1)
    lon1 = math.radians(lon1)
    lat2 = math.radians(lat2)
    lon2 = math.radians(lon2)

    # 차이 계산
    dlat = lat2 - lat1
    dlon = lon2 - lon1

    # Haversine 공식 적용
    a = math.sin(dlat / 2)**2 + math.cos(lat1) * math.cos(lat2) * math.sin(dlon / 2)**2
    c = 2 * math.atan2(math.sqrt(a), math.sqrt(1 - a))

    # 거리 계산
    distance = R * c

    return distance





    