import mouse
import keyboard
import requests
from bs4 import BeautifulSoup
from selenium import webdriver
from selenium.webdriver.common.by import By
import time
from googletrans import Translator
from langdetect import detect
import os
import json
import re
import base64

# global defs
def wait(wait_time: int = 1):
    time.sleep(wait_time)

def clear():
    os.system("cls")

def filesIn(folder_path: str):
    return os.listdir(folder_path)

def detect_lang(text: str, detectOrSrc: int = 0):
    try:
        if detectOrSrc == 0: return detect(text)
        else: return Translator().translate(text).src
    except:
        return 404

def saveJson(data: dict, file_path: str, indent: int = 4, encoding: str = "utf-8"):
    with open(file_path, 'w', encoding=encoding) as output_file:
        json.dump(data, output_file, indent=indent, ensure_ascii=False)

def saveFile(data, file_path: str, encoding: str = "utf-8"):
    with open(file_path, 'w', encoding=encoding) as output_file:
        output_file.write(data)

def readFile(file_path: str, encoding: str = "utf-8"):
    data = 404
    try:
        with open(file_path, 'r', encoding=encoding) as file:
            data = json.load(file)
            return data
    except:
        return 404

# arr defs
def equalArr(arr: list, dimensions: int = 1): # arr1 = arr2
    '''
    equalArr
    -----------------
    عند عمل مصفوفة واستنساخها:
    ```
    arr1 = [1, 2, 3]
    arr2 = arr1
    ```
    فإن أي تعديل تقوم به على المصفوفة الثانية يُغيّر أيضا في المصفوفة الأولى،
    ولهذا جاءت هذه الدالة: لتقوم باستنساخ المصفوفة بدون مشاكل، فقط عليك ان تدخل عدد ابعاد المصفوفة

    ```
    
    ...
    ```

    مثلا:
    ------
    ```
    [1, 2, 3] # هذه المصفوفة لديها بُعد واحد
    [[1], [2], [3]] # هذه المصفوفة لديها بُعدان
    ```
    وهكذا

    ```
    
    ...
    ```
    
    مثال:
    -----------
    ```
    arr1 = [1, 2, 3]
    arr2 = equalArr(arr1, 1)

    arr2.append(4) # إضافة رقم 4 للمصفوفة الثانية

    print(arr1) # ==> [1, 2, 3]
    print(arr2) # ==> [1, 2, 3, 4]
    ```
    '''
    res = []

    if str(type(arr)) == "<class 'list'>":
        i = 0
        while i < len(arr):
            if dimensions == 1:
                res.append(arr[i])
            elif dimensions > 1 and str(type(arr[i])) == "<class 'list'>":
                res.append([])
                res[i] = equalArr(arr[i], dimensions-1)
            else: # في حالة الخطأ
                res.append(arr[i])
            i += 1

    return res

def allIndexInArr(arr: list, item): # 1D arr=[10,20,30,40,30], item=30 ==> [2,4]
	allIndex = []
	
	i = 0
	while i < len(arr):
		if arr[i] == item:
			allIndex.append(i)
		i += 1
	
	return allIndex

def inArr(arr: list, item): # ==> True, False
	return item in arr

def onSide(arr: list, item, greater: int = 1, startFrom: int = 0): # 2D arr=[1,1,1,3,2,2,2,1,2,2], item=2, greater=1 ==> [ [4,5,6], [8,9] ] ==> [ indexArr ]
	finalArr = []
	onceArr = []
	
	i = startFrom
	while i < len(arr):
		if arr[i] == item:
			onceArr.append(i)
		else:
			if len(onceArr) > greater:
				finalArr.append(onceArr)
				
			onceArr = []
		i += 1
	
	if len(onceArr) > greater:
		finalArr.append(onceArr)
		onceArr = []
		
	return finalArr

def fillArr(arr: list, fill, fromNum: int = -1, toNum: int = -1): # arr=["⬜","⬜","⬜"], fill="🟩", from=0,to=1 ==> ["🟩","🟩","⬜"] #
	arr = equalArr(arr)
	
	if fromNum == -1:
		fromNum = 0
	if toNum == -1:
		toNum = len(arr)-1
	
	if fromNum < len(arr) and toNum < len(arr):
		i = fromNum
		while i < toNum+1:
			arr[i] = fill
			i += 1
	
	return arr

def removeFromArr(arr: list, index: int = -1, txt = "", minusOne: int = 0):
	newArr = []
	i = 0
	if (index > -1) or (index <= -1 and minusOne != 0):
		while i < len(arr):
			if i != index%len(arr):
				newArr.append(arr[i])
			i += 1
	else:
		counter = 0
		while i < len(arr):
			if arr[i] != txt or (arr[i] == txt and counter == 1):
				newArr.append(arr[i])
		
			if counter == 0 and arr[i] == txt:
				counter = 1
			
			i += 1
		
	return newArr

def RemoveDuplicates(arr: list, removeOriginalItem: int = 0, spicificItem: int = -1, item = None):
	arr = equalArr(arr)
	
	i = 0
	continu = 1
	while i < len(arr) and (spicificItem == -1 or (spicificItem != -1 and continu == 1)):
		allInd = []
		if spicificItem == -1:
			allInd = allIndexInArr(arr, arr[i])
		else:
			allInd = allIndexInArr(arr, item)
		
		if len(allInd) > 1:
			e = len(allInd)-1
			while e > 0:
				arr = removeFromArr(arr, allInd[e])
				if spicificItem != -1:
					continu = 0
				e -= 1
					
			if removeOriginalItem > 0:
				arr = removeFromArr(arr, allInd[0])
				i -= 1
					
		i += 1
			
	return arr

def sumArr(arr: list, fromNum: int = -1, toNum: int = -1): # arr=[1,2,3], fromNum=0, toNum=1 ==> 1+2 = 3
	sum = 0
	if fromNum > -1 and toNum > -1:
		toNum += 1
	elif fromNum > -1 and toNum < 0:
		toNum = len(arr)
	elif fromNum < 0 and toNum < 0:
		fromNum = 0
		toNum = len(arr)
		
	while fromNum < toNum:
		sum += arr[fromNum]
		fromNum += 1
	
	return sum

def minusArr(arr: list): # [1,4,5,8,7] ==> [3,1,3,-1]
	res = []

	i = 1
	while i < len(arr):
		res.append(arr[i] - arr[i-1])
		i += 1
	
	return res

def makeColumnArr(arr: list, column: int = 0): # convert column in 2D arr to arr
	arrLen = len(arr)
	columnArr = []

	i = 0
	while i < arrLen:
		if column < len(arr[i]): columnArr.append(arr[i][column])
		i += 1

	return columnArr

def convertRowToCol(arr: list, columnArr: list, column: int = 0): # Col ==> column, columnArr ==> row
    i = 0
    while i < len(columnArr):
        arr[i][column] = columnArr[i]
        i += 1
    
    return arr

def addArr(fromArr: list, toArr: list): # fromArr=[4,5,6], toArr=[1,2,3] ==> [1,2,3,4,5,6]
	toArr = equalArr(toArr)
	for x in fromArr:
		toArr.append(x)

	return toArr

def delArr(smallArr: list, bigArr: list): # smallArr = [4,5,6], bigArr=[1,2,3,4,5,6] ==> [1,2,3]
	smallArr = equalArr(smallArr)
	bigArr = equalArr(bigArr)

	for x in smallArr:
		if inArr(bigArr, x):
			bigArr = removeFromArr(bigArr, -1, x)
	
	return bigArr

def sliceArr(arr: list, fromNum: int = 0, toNum: int = 0): # arr=[10,20,30,40,50], fromNum=1, toNum=3 ==> [20,30,40]
    newArr = []

    i = fromNum
    while i <= toNum:
        newArr.append(arr[i])
        i += 1
    
    return newArr

def reverseArr(arr: list): # arr=[1,2,3] ==> [3,2,1]
    return arr[::-1]

def search2D(arr: list, item, column: int = 0): # ==> [index]
	res = []

	i = 0
	while i < len(arr):
		if arr[i][column] == item:
			res.append(i)
		i += 1

	return res

def convert2D(arr: list): # ==> 1D, convert 2D to 1D, arr=[ [1,2,3], [4,5,6], [7,8,9] ] ==> [1,2,3,4,5,6,7,8,9]
	newArr = []
	
	i = 0
	while i < len(arr):
		e = 0
		while e < len(arr[i]):
			newArr.append(arr[i][e])
			e += 1
		i += 1
	
	return newArr

def rotate2DArr(arr: list, numR: int = 1): # numR ==> num of right turns
    arr = equalArr(arr, 2)
    
    a = 0
    while a < numR:
        res = []

        i = 0
        while i < len(arr[0]):
            res.append(makeColumnArr(arr, i)[::-1])
            i += 1
        
        arr = res
        a += 1
    
    return arr

def insertInArr(arr: list, itemToInsert, itemIndex: int = 0, isMultiItems: int = 0): # arr=[1,3,4], itemToInsert=2 ==> [1,2,3,4]
    arr = equalArr(arr)
    res = []

    i = 0
    while i < len(arr):
        if i == itemIndex:
            if isMultiItems == 0: res.append(itemToInsert)
            elif "list" in str(type(itemToInsert)):
                e = 0
                while e < len(itemToInsert):
                    res.append(itemToInsert[e])
                    e += 1
        res.append(arr[i])
        i += 1

    return res

def convertTxt(text: str): # convert text to code, "5 + 7" ==> 12
    try:
        return eval(text)
    except Exception as e:
        print("حدث خطأ في التحويل:", e)
        return None

def searchIn_decodeNum(arr: list, search_key, equalOrContain: int = 0): # arr=["n1", "o1", "n2"], search_key="n" ==> [0, 2] => [index]
    arr = equalArr(arr)
    result = []
    
    newArr = []
    i = 0
    while i < len(arr):
        newArr.append(decodeNum(arr[i]))
        for index, item in enumerate(newArr[i]):
            newArr[i][index] = str(item)

        if (equalOrContain == 0 and search_key in newArr[i]) or (equalOrContain == 1 and search_key in "".join(newArr[i])):
            result.append(i)
        i += 1

    return result

def searchInObjArr(arr: list, key_want_search: str, value_want_search): # [ {"id": 1, "num": 453}, {"id": 2, "num": 734} ], id == 1 ==> [  [ {"id": 1, "num": 453} ], [0]  ]
    res = []
    ind = []
    for i, item in enumerate(arr):
        if type(item) == dict:
            if key_want_search in item:
                if item[key_want_search] == value_want_search:
                    res.append(item)
                    ind.append(i)
    
    return [res, ind]

# object {} defs
def equalObject(the_object: dict, dimensions: int = 1): # the_object1 = the_object2
    '''
    equalObject
    -----------------
    عند عمل قاموس واستنساخه:
    ```
    obj1 = {"number": 1}
    obj2 = obj1
    ```
    فإن أي تعديل تقوم به على القاموس الثاني يُغيّر أيضا في القاموس الأول،
    ولهذا جاءت هذه الدالة: لتقوم باستنساخ القاموس بدون مشاكل، فقط عليك ان تدخل عدد ابعاد القاموس

    ```
    
    ...
    ```

    مثلا:
    ------
    ```
    {"number": 1} # هذا القاموس لديه بُعد واحد
    {"number": {"number2": 2}} # هذا القاموس لديه بُعدان
    ```
    وهكذا

    ```
    
    ...
    ```
    
    مثال:
    -----------
    ```
    obj1 = {"numbers":  {"n1": 1, "n2": 2, "n3": 3}  }
    obj2 = equalObject(obj1, 2) # القاموس الأول لديه بُعدان

    obj2["numbers"]["n4"] = 4 # إضافة رقم 4 للقاموس الثاني

    print(obj1) # ==> {"numbers": {"n1": 1, "n2": 2, "n3": 3}}
    print(obj2) # ==> {"numbers": {"n1": 1, "n2": 2, "n3": 3, 4}}
    ```
    '''
    res = {}
    if str(type(the_object)) == "<class 'dict'>":
        for key in the_object:
            item = the_object[key]
            if dimensions == 1:
                res[key] = item
            elif dimensions > 1 and str(type(item)) == "<class 'dict'>":
                res[key] = equalObject(item, dimensions-1)
            else: # في حالة الخطأ
                res[key] = item

    return res

def getObjectKeys(the_object: dict): # {"h1": "text", "h2": "text2"} ==> ["h1", "h2"]
    keys = []
    for key in the_object:
        keys.append(key)
    
    return keys

def addToObject(the_object: dict, key: str, want_to_add): # {"name": "about"}, "name", "about" ==> {"name": "about", "name 2": "about"}
    the_object = equalObject(the_object)
    if len(searchIn_decodeNum(getObjectKeys(the_object), key, 1)) > 0:
        the_object[f"{key} {len(searchIn_decodeNum(getObjectKeys(the_object), key, 1))+1}"] = want_to_add
    else:
        the_object[key] = want_to_add

    return the_object

# json defs
def combine_json_files(files_names: list, output_file_path: str, indent: int = 4, encoding: str = "utf-8"):
    combined_data = {}

    # قراءة محتوى كل ملف وإضافة المعلومات إلى القاموس
    for file_name in files_names:
        data = readFile(file_name, encoding)
        combined_data.update(data)

    # حفظ الملف
    saveJson(combined_data, output_file_path, indent, encoding)

def uncombine_json_file(combined_file_path: str, indent: int = 4, encoding: str = "utf-8", files: str = 'f"{key}.json"'):
    # قراءة الملف
    combined_data = readFile(combined_file_path, encoding)

    # حفظ الملفات
    for key in combined_data:
        saveJson({key:combined_data[key]}, eval(files), indent, encoding)

# text defs
def decodeNum(text: str): # r2 ==> ["r", 2]
    res = []
    tmp = ""
    nums = "0123456789"

    i = 0
    while i < len(text)-1:
        tmp += text[i]
        if text[i] not in nums:
            if text[i+1] in nums:
                res.append(tmp)
                tmp = ""
        else:
            if text[i+1] not in nums:
                res.append(int(tmp))
                tmp = ""
        i += 1

    tmp += text[i]
    if text[i] in nums: res.append(int(tmp))
    if text[i] not in nums: res.append(tmp)
    tmp = ""

    return res

def right(text: str, amount: int):
    return text[-amount:]

def left(text: str, amount: int):
    return text[:amount]

def mid(text: str, offset: int, amount: int):
    return text[offset:offset+amount]

def split(text: str, splitter : str= ""):
    '''
    هذه الدالة تقسم النص
    '''
    
    res = []
    if splitter == "":
        for char in text:
            res.append(char)
    else:
        res = text.split(splitter)
    
    return res

def removeSpaces(text: str): # إلغاء   المسافات
    text = " ".join(RemoveDuplicates(text.split(" "), 1, 1, ""))
    text = " ".join(removeFromArr(text.split(" "), -1, ""))
    return text.strip()

def replaceText(text: str, replArr: list): # text = "hello word!", replArr = [ ["word", "world"], ["!", "."] ] ==> "hello world."
    i = 0
    while i < len(replArr):
        text = text.replace(replArr[i][0], replArr[i][1])
        i += 1

    return text

# separate defs
def separate_text(text: str, splitter: list = ["."], joiner: str = "\n", stripping: str = 0, delete_splitter: int = 0): # "line1.line2" ==> "line1.\nline2"
    if str(type(splitter)) == "<class 'list'>":
        for item in splitter:
            tmp = text.split(item)
            if stripping == 1:
                for tmp_i, tmpItem in enumerate(tmp): tmp[tmp_i] = tmpItem.strip()
            
            if delete_splitter == 0: text = f"{item}{joiner}".join(tmp)
            else: text = joiner.join(tmp)
    else:
        tmp = text.split(str(splitter))
        if stripping == 1:
            for tmp_i, tmpItem in enumerate(tmp): tmp[tmp_i] = tmpItem.strip()
        
        if delete_splitter == 0: text = f"{splitter}{joiner}".join(tmp)
        else: text = joiner.join(tmp)
    
    return text

def separate_arr(arr: list, splitter: list = ["."], joiner: str = "\n", stripping: str = 0, delete_splitter: int = 0):
    new_arr = []

    for item in arr:
        if str(type(item)) == "<class 'str'>":
            new_arr.append(separate_text(item, splitter, joiner, stripping, delete_splitter))
        elif str(type(item)) == "<class 'list'>":
            new_arr.append(separate_arr(item, splitter, joiner, stripping))
        elif str(type(item)) == "<class 'dict'>":
            new_arr.append(separate_obj(item, splitter, joiner, stripping))
        else:
            new_arr.append(item)
    
    return new_arr

def separate_obj(obj: dict, splitter: list = ["."], joiner: str = "\n", stripping: int = 0, delete_splitter: int = 0):
    new_obj = {}

    for key in obj:
        item = obj[key]
        if str(type(item)) == "<class 'str'>":
            new_obj[key] = separate_text(item, splitter, joiner, stripping, delete_splitter)
        elif str(type(item)) == "<class 'list'>":
            new_obj[key] = separate_arr(item, splitter, joiner, stripping)
        elif str(type(item)) == "<class 'dict'>":
            new_obj[key] = separate_obj(item, splitter, joiner, stripping)
        else:
            new_obj[key] = item
    
    return new_obj

# translate defs
def translate_text(text, target_language: str ="en", not_langs: list = [], detectOrSrc: int = 0):
    translator = Translator()

    if not_langs == []:
        if text:
            textArr = []
            for i in text.split("\n"):
                if i != "":
                    tmp = ""
                    try:
                        tmp = translator.translate(i, dest=target_language)
                        textArr.append(tmp.text)
                    except:
                        tmp = i
                        textArr.append(tmp)

            translation = "\n".join(textArr)
            return translation
    else:
        sentences = text.split(" ")
    
        fullArr = []
        fullArrInd = []
        tmpArr = []
        tmpInd = -1
        # ["hello", "اهلا"] ==> fullArr = [ ["hello"] ], fullArrInd = [ 0 ]
        detected_language = ""
        for i, item in enumerate(sentences):
            detected_language = detect_lang(item, detectOrSrc)
            if detected_language != 404:
                if detected_language not in not_langs:
                    tmpArr.append(item)
                    if tmpInd == -1: tmpInd = i
                elif i != 0 and len(tmpArr) > 0:
                    fullArr.append(tmpArr)
                    fullArrInd.append(tmpInd)
                    tmpArr = []
                    tmpInd = -1
            else:
                fullArr.append(item)
                fullArrInd.append(i)
                tmpArr = []
                tmpInd = -1
                detected_language = ""
        if detected_language not in not_langs and len(tmpArr) > 0:
            fullArr.append(tmpArr)
            fullArrInd.append(tmpInd)
            tmpArr = []
            tmpInd = -1
        
        for i, item in enumerate(fullArr):
            i = len(fullArr)-i-1
            for e in range(len(fullArr[i])):
                sentences = removeFromArr(sentences, fullArrInd[i])
            sentences.insert(fullArrInd[i], fullArr[i])

        for i, item in enumerate(sentences):
            if str(type(item)) == "<class 'list'>":
                tmpTransText = " ".join(item)
                tmpTrans = ""
                try: tmpTrans = translator.translate(tmpTransText, dest=target_language).text
                except: tmpTrans = tmpTransText
                sentences[i] = tmpTrans

        text = " ".join(sentences)
    
    return text

def translate_arr(arr, target_language: str ="en", not_langs: list = []):
    newArr = []
    for item in arr:
        if str(type(item)) == "<class 'str'>":
            newArr.append(translate_text(item, target_language, not_langs))
        elif str(type(item)) == "<class 'dict'>": # dict ==> {}
            newArr.append(translate_obj(item, target_language, not_langs))
        elif str(type(item)) == "<class 'list'>": # list ==> []
            newArr.append(translate_arr(item, target_language, not_langs))
        else:
            newArr.append(item)
        
    return newArr

def translate_obj(obj: dict, target_language: str = "en", not_langs: list = []):
    new_obj = {}

    for i in obj:
        if str(type(obj[i])) == "<class 'str'>":
            new_obj[translate_text(i, target_language, not_langs)] = translate_text(obj[i], target_language, not_langs)
        elif str(type(obj[i])) == "<class 'dict'>": # dict ==> {}
            new_obj[translate_text(i, target_language, not_langs)] = translate_obj(obj[i], target_language, not_langs)
        elif str(type(obj[i])) == "<class 'list'>": # list ==> []
            new_obj[translate_text(i, target_language, not_langs)] = translate_arr(obj[i], target_language, not_langs)
        else:
            new_obj[translate_text(i, target_language, not_langs)] = obj[i]

    return new_obj

# decode html
def decode_html_in_text(text: str, tag: str = "img", attribute: str = "src", endTag: int = 0): # <img src="the_link.com"> ==> the_link.com
    if attribute != "":
        check = rf'<{tag} .*?{attribute}="([^"]*)".*?>'
        if endTag == 1: check += rf'.*?</{tag}>'
    else: # inner html
        check = rf'<.*?{tag}.*?>([^"]*)'
        if endTag == 1: check += rf'</{tag}>'

    return re.sub(check, r'\1', text)

def decode_html_in_arr(arr: list, tag: str = "img", attribute: str = "src", endTag: int = 0):
    newArr = []
    for item in arr:
        if str(type(item)) == "<class 'list'>":
            newArr.append(decode_html_in_arr(item, tag, attribute, endTag))
        elif str(type(item)) == "<class 'dict'>":
            newArr.append(decode_html_in_obj(item, tag, attribute, endTag))
        elif str(type(item)) == "<class 'str'>":
            newArr.append(decode_html_in_text(item, tag, attribute, endTag))
        else:
            newArr.append(item)

    return newArr

def decode_html_in_obj(obj: dict, tag: str = "img", attribute: str = "src", endTag: int = 0):
    newObj = {}
    for key in obj:
        item = obj[key]
        if str(type(item)) == "<class 'list'>":
            newObj[key] = decode_html_in_arr(item, tag, attribute, endTag)
        elif str(type(item)) == "<class 'dict'>":
            newObj[key] = decode_html_in_obj(item, tag, attribute, endTag)
        elif str(type(item)) == "<class 'str'>":
            newObj[key] = decode_html_in_text(item, tag, attribute, endTag)
        else:
            newObj[key] = item
    
    return newObj

# mouse defs
def getMousePosition():
    return [mouse.get_position()[0], mouse.get_position()[1]]

def getMousePositions(saveKey: str ="g", breakKey: str ="0"):
    all = []

    while True:
        if keyboard.is_pressed(saveKey):
            mouse_position = getMousePosition()
            item = input("what is it?\n")
            all.append([ item, mouse_position ])

        if keyboard.is_pressed(breakKey):
            break

    print(all)

# soup
def get_soup(link: str, wait: int = 0):
    headers = {
        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.3'
    }

    with requests.Session() as session:
        try:
            result = session.get(link, headers=headers, timeout=10)
            time.sleep(wait)
            result.raise_for_status()  # رفع استثناء إذا كان هناك خطأ في الاستجابة
            src = result.content
            soup = BeautifulSoup(src, "lxml")
            return soup
        except requests.exceptions.RequestException as e:
            # print(f"Error: {e}")
            return None

def get_redirected_url(link: str): # bit.ly ==> link
    # إجراء طلب GET للحصول على الصفحة الأولى
    response = requests.get(link)

    # التحقق من وجود عملية تحويل
    if response.history:
        # الحصول على الرابط النهائي بعد التحويل
        final_url = response.url
        return final_url
    else:
        return link

# selenium
def makeFirefoxDriver():
    options = webdriver.FirefoxOptions()
    options.add_argument('--headless')
    driver = webdriver.Firefox()

    return driver

def driverToSoup(driver):
    return BeautifulSoup(driver.page_source, "html.parser")

# dataURL
def imageLink_to_dataurl(image_url, extension = "png", error = 404):
    # تنزيل الصورة من الرابط
    response = requests.get(image_url)
    
    # التحقق من أن الاستجابة صحيحة
    if response.status_code == 200:
        data_url = f'data:image/{extension};base64,' + base64.b64encode(response.content).decode('utf-8')
        return data_url
    else:
        return error

# done message
def doneMessage(want_clear: int = 1):
    if want_clear > 0: clear()
    print("Done.. Alhamdulillah")