import os,sys, math, winreg
import RLPy
from PySide2 import QtWidgets
from PySide2.QtCore import Qt
from PySide2.QtCore import QTimer
from shiboken2 import wrapInstance

#Programmed by Tesira

try:
    # 1. Wir öffnen den offiziellen iClone 8 Registry-Schlüssel
    hKey = winreg.OpenKey(winreg.HKEY_LOCAL_MACHINE, r"SOFTWARE\Reallusion\iClone\8.0")
    
    # 2. iClone 8 speichert den Standard-Pfad unter dem Wert "Template Data"
    raw_path = winreg.QueryValueEx(hKey, "Template Data")[0]
    ic_template_path = os.path.abspath(raw_path)
    
    print("--------------------------------------------------")
    print(f"Dein iClone 8 Template-Pfad lautet:")
    print(ic_template_path)
    print("--------------------------------------------------")

except Exception as e:
    print("--------------------------------------------------")
    print(f"Registry-Abfrage fehlgeschlagen: {e}")
    # Sicherheits-Fallback, falls die Registry blockiert ist:
    ic_template_path = r"C:\Users\Public\Documents\Reallusion\Reallusion Templates"
    print(f"Nutze Standard-Fallback: {ic_template_path}")
    print("--------------------------------------------------")

rl_plugin_info = None
ap_version=RLPy.RApplication.GetProductVersion()[0]

if ap_version == 8:
    rl_plugin_info = {"ap": "iClone", "ap_version": "8.0"}

my_template_path = "your template path" 


xtile=1.0
ytile=1.0
scalewa=1.0
scalewo=1.0
scalebe=1.0

def keys(i):
    key = RLPy.RKey()
    ticks = int(i * 6)
    key.SetTime(RLPy.RTime.FromValue(ticks))
    key.SetTransitionType(RLPy.ETransitionType_Linear)
    key.SetTransitionStrength(100)
    return key

key1=keys(0)

def loadtexture(p,k,t):
    material_component = p.GetMaterialComponent()
    mesh_list = p.GetMeshNames()
    mesh_name = mesh_list[0]
    material_list = material_component.GetMaterialNames(mesh_name)
    material_name = material_list[0]
    texture_channel = RLPy.EMaterialTextureChannel_Diffuse
    
    result = material_component.LoadImageToTexture(mesh_name, material_name, texture_channel, t)
    diff_tile = RLPy.RVector2(1.00, 1.00)
    diff_rotate = 0.00
    diff_offset = RLPy.RVector2(0, 0)
    material_component.AddUvDataKey(k, mesh_name, material_name, 
                                texture_channel, diff_offset, diff_tile, diff_rotate)

def loadbump(p,k,t):
    material_component = p.GetMaterialComponent()
    mesh_list = p.GetMeshNames()
    mesh_name = mesh_list[0]
    material_list = material_component.GetMaterialNames(mesh_name)
    material_name = material_list[0]
    texture_channel = RLPy.EMaterialTextureChannel_Bump
   
    result = material_component.LoadImageToTexture(mesh_name, material_name, texture_channel, t)
    diff_tile = RLPy.RVector2(1.00, 1.00)
    diff_rotate = 0.00
    diff_offset = RLPy.RVector2(0, 0)
    material_component.AddUvDataKey(k, mesh_name, material_name, 
                                texture_channel, diff_offset, diff_tile, diff_rotate)

def loadmetallic(p,k,t):
    material_component = p.GetMaterialComponent()
    mesh_list = p.GetMeshNames()
    mesh_name = mesh_list[0]
    material_list = material_component.GetMaterialNames(mesh_name)
    material_name = material_list[0]
    texture_channel = RLPy.EMaterialTextureChannel_Metallic
    
    result = material_component.LoadImageToTexture(mesh_name, material_name, texture_channel, t)
    diff_tile = RLPy.RVector2(1.00, 1.00)
    diff_rotate = 0.00
    diff_offset = RLPy.RVector2(0, 0)
    material_component.AddUvDataKey(k, mesh_name, material_name, 
                                texture_channel, diff_offset, diff_tile, diff_rotate)

def loaddisplacement(p,k,t):
    material_component = p.GetMaterialComponent()
    mesh_list = p.GetMeshNames()
    mesh_name = mesh_list[0]
    material_list = material_component.GetMaterialNames(mesh_name)
    material_name = material_list[0]
    texture_channel = RLPy.EMaterialTextureChannel_Displacement
    
    result = material_component.LoadImageToTexture(mesh_name, material_name, texture_channel, t)
    diff_tile = RLPy.RVector2(1.00, 1.00)
    diff_rotate = 0.00
    diff_offset = RLPy.RVector2(0, 0)
    material_component.AddUvDataKey(k, mesh_name, material_name, 
                                texture_channel, diff_offset, diff_tile, diff_rotate)

def loadopacity(p,k,t):
    material_component = p.GetMaterialComponent()
    mesh_list = p.GetMeshNames()
    mesh_name = mesh_list[0]
    material_list = material_component.GetMaterialNames(mesh_name)
    material_name = material_list[0]
    texture_channel = RLPy.EMaterialTextureChannel_Opacity
    result = material_component.LoadImageToTexture(mesh_name, material_name, texture_channel, t)
    diff_tile = RLPy.RVector2(1.00, 1.00)
    diff_rotate = 0.00
    diff_offset = RLPy.RVector2(0, 0)
    material_component.AddUvDataKey(k, mesh_name, material_name, 
                                texture_channel, diff_offset, diff_tile, diff_rotate)


def texture_offset_channel(p,t,ch,off1,off2,til1,til2):
    material_component = p.GetMaterialComponent()
    mesh_list = p.GetMeshNames()
    mesh_name = mesh_list[0]
    material_list = material_component.GetMaterialNames(mesh_name)
    material_name = material_list[0]

    if ch=="Diffuse":
        texture_channel = RLPy.EMaterialTextureChannel_Diffuse
    elif ch=="Bump":
        texture_channel = RLPy.EMaterialTextureChannel_Bump
    elif ch=="Metallic":
        texture_channel = RLPy.EMaterialTextureChannel_Metallic
    elif ch=="Displacement":
        texture_channel = RLPy.EMaterialTextureChannel_Displacement
    elif ch=="Opacity":
        texture_channel = RLPy.EMaterialTextureChannel_Opacity
    elif ch=="DiffuseBlend":
         texture_channel = RLPy.EMaterialTextureChannel_DiffuseBlend
    diff_tile = RLPy.RVector2(til1, til2)
    diff_rotate = 0.00
    diff_offset = RLPy.RVector2(off1, off2)
    key1=keys(t)
  
    material_component.AddUvDataKey(key1, mesh_name, material_name, texture_channel, diff_offset, diff_tile, diff_rotate)
    
    temp_uv_offset = RLPy.RVector2()
    temp_uv_tile = RLPy.RVector2()  
    temp_uv_data = material_component.GetUvData(mesh_name, material_name, texture_channel, temp_uv_offset, temp_uv_tile, diff_rotate)
    
    if ch=="Diffuse":
        print("offset")
        print(temp_uv_offset[0])   
        print(temp_uv_offset[1])
    
        off1=temp_uv_offset[0]
        off2=temp_uv_offset[1]
        s=off2+off1  
        if s==0.0:
            d=0.0
        else:
            d=off1/s
        print("s")
        print(s)
        print("d")
        print(d)

def texture_get_offset_channel(p,ch,fu):
    material_component = p.GetMaterialComponent()
    mesh_list = p.GetMeshNames()
    mesh_name = mesh_list[0]
    material_list = material_component.GetMaterialNames(mesh_name)
    material_name = material_list[0]
    
    if ch=="Diffuse":
        texture_channel = RLPy.EMaterialTextureChannel_Diffuse
    elif ch=="Bump":
        texture_channel = RLPy.EMaterialTextureChannel_Bump
    elif ch=="Metallic":
        texture_channel = RLPy.EMaterialTextureChannel_Metallic
    elif ch=="Displacement":
        texture_channel = RLPy.EMaterialTextureChannel_Displacement
    elif ch=="Opacity":
        texture_channel = RLPy.EMaterialTextureChannel_Opacity
    elif ch=="DiffuseBlend":
         texture_channel = RLPy.EMaterialTextureChannel_DiffuseBlend
    
    diff_rotate = 0.00
    temp_uv_offset = RLPy.RVector2()
    temp_uv_tile = RLPy.RVector2()  
    temp_uv_data = material_component.GetUvData(mesh_name, material_name, texture_channel, temp_uv_offset, temp_uv_tile, diff_rotate)
    
   
    if fu==1:
        return temp_uv_offset
    if fu==2:
        return temp_uv_tile

# update of the high and scale of the selected prop  __________________________________________________________________________________________________________
def update_scale_high(parameter, value):
    print("scale and high")

def cloudini1(j): 
    print("cloudini")

def waterini(j): 
    print("waterini")
    #Timeline sofort stoppen, falls sie läuft
    if RLPy.RGlobal.IsPlaying():
        RLPy.RGlobal.Stop()
    RLPy.RGlobal.SetTime(RLPy.RTime.FromValue(0))
    prop = RLPy.RScene.FindObject(RLPy.EObjectType_Prop, "Water1")
    if not prop:
        print("Script Log: Aktion ignoriert. Kein Wasser-Prop in der Szene.")
        return 
    else:
        RLPy.RScene.SelectObject(prop)
        
        if j<20:
            
            image_file= my_template_path +"Waves//wave_"
            image_file+=str(j)
            image_file+=".jpg"
            print(image_file)
            loadtexture(prop,key1,image_file)
            loadmetallic(prop,key1,image_file)
            image_file= my_template_path +"Waves//wave_"
            image_file+=str(j)
            image_file+="soft.jpg"
            loadbump(prop,key1,image_file)
            loaddisplacement(prop,key1,image_file)
            image_file = my_template_path + "Waves//wave_Opacity.jpg"
            loadopacity(prop,key1,image_file)
            texture_offset_channel(prop,0,"Diffuse",0.0,0.0,xtile,ytile)
            texture_offset_channel(prop,0,"Bump",0.0,0.0,xtile,ytile)
            texture_offset_channel(prop,0,"Displacement",0.0,0.0,xtile,ytile)
            texture_offset_channel(prop,0,"Opacity",0.0,0.0,xtile,ytile)
            texture_offset_channel(prop,30000,"Diffuse",0.5,0.5,xtile,ytile)
            texture_offset_channel(prop,30000,"Bump",-0.4,-0.4,xtile,ytile)
            texture_offset_channel(prop,30000,"Displacement",0.49,0.49,xtile,ytile)
            texture_offset_channel(prop,30000,"Opacity",0.5,0.5,xtile,ytile)

       
        fps = RLPy.RGlobal.GetFps()
        current_time = RLPy.RGlobal.GetTime()

        # 1. Aktuellen Frame als Zahl auslesen
        current_frame = RLPy.GetFrameIndex(current_time, fps)

        # 2. Einen Frame draufrechnen und via IndexedFrameTime in die richtige Zeit konvertieren
        next_frame_time = RLPy.IndexedFrameTime(current_frame + 1, fps)
        RLPy.RGlobal.SetTime(next_frame_time)

        fps = RLPy.RGlobal.GetFps()
        current_time = RLPy.RGlobal.GetTime()

        # 1. Aktuellen Frame als Zahl auslesen
        current_frame = RLPy.GetFrameIndex(current_time, fps)

        # 2. Einen Frame abziehen und via IndexedFrameTime in die richtige Zeit konvertieren
        prev_frame_time = RLPy.IndexedFrameTime(current_frame - 1, fps)
        RLPy.RGlobal.SetTime(prev_frame_time)

def waterini2(j): #für unteres Wasser
    print("waterini2")
    
    # Timeline sofort stoppen, falls sie läuft
    if RLPy.RGlobal.IsPlaying():
        RLPy.RGlobal.Stop()
    RLPy.RGlobal.SetTime(RLPy.RTime.FromValue(0))
    prop = RLPy.RScene.FindObject(RLPy.EObjectType_Prop, "Water1_0")
    if not prop:
        print("Script Log: Aktion ignoriert. Kein Wasser-Prop in der Szene.")
        return # Schläft sofort wieder ein!
    else: 
        RLPy.RScene.SelectObject(prop)
        
        if j<20:
            
            image_file= my_template_path +"Waves//wave_"
            image_file+=str(j)
            image_file+=".jpg"
            print(image_file)
            loadtexture(prop,key1,image_file)
            loadmetallic(prop,key1,image_file)
            image_file= my_template_path +"Waves//wave_"
            image_file+=str(j)
            image_file+="soft.jpg"
            loadbump(prop,key1,image_file)
            loaddisplacement(prop,key1,image_file)
            image_file = my_template_path + "Waves//wave_Opacity.jpg"
            loadopacity(prop,key1,image_file)
            texture_offset_channel(prop,0,"Diffuse",0.0,0.0,xtile,ytile)
            texture_offset_channel(prop,0,"Bump",0.0,0.0,xtile,ytile)
            texture_offset_channel(prop,0,"Displacement",0.0,0.0,xtile,ytile)
            texture_offset_channel(prop,0,"Opacity",0.0,0.0,xtile,ytile)
            texture_offset_channel(prop,30000,"Diffuse",0.5,0.5,xtile,ytile)
            texture_offset_channel(prop,30000,"Bump",-0.4,-0.4,xtile,ytile)
            texture_offset_channel(prop,30000,"Displacement",0.49,0.49,xtile,ytile)
            texture_offset_channel(prop,30000,"Opacity",0.5,0.5,xtile,ytile)
        
        fps = RLPy.RGlobal.GetFps()
        current_time = RLPy.RGlobal.GetTime()

        # 1. Aktuellen Frame als Zahl auslesen
        current_frame = RLPy.GetFrameIndex(current_time, fps)

        # 2. Einen Frame draufrechnen und via IndexedFrameTime in die richtige Zeit konvertieren
        next_frame_time = RLPy.IndexedFrameTime(current_frame + 1, fps)
        RLPy.RGlobal.SetTime(next_frame_time)

        fps = RLPy.RGlobal.GetFps()
        current_time = RLPy.RGlobal.GetTime()

        # 1. Aktuellen Frame als Zahl auslesen
        current_frame = RLPy.GetFrameIndex(current_time, fps)

        # 2. Einen Frame abziehen und via IndexedFrameTime in die richtige Zeit konvertieren
        prev_frame_time = RLPy.IndexedFrameTime(current_frame - 1, fps)
        RLPy.RGlobal.SetTime(prev_frame_time)


def skyini(j): 
    print("skyini")

def clear_script_props():
    print("Script Log: Starte radikale Szene-Reinigung...")
    
    # Eine Liste mit den Namen all deiner Props
    props_to_remove = ["Water1", "Water1_0", "Wolke1", "Wolke2", "Beach1"]
    
    # Erst die Timeline stoppen, falls sie noch läuft (Stresstest-Sicherung!)
    if RLPy.RGlobal.IsPlaying():
        RLPy.RGlobal.Stop()
    
    # Auf Frame 0 zurücksetzen für die saubere Leere
    RLPy.RGlobal.SetTime(RLPy.RTime.FromValue(0))
    
    # Schleife rennt durch die Namen und löscht die Objekte nacheinander aus der Szene
    for prop_name in props_to_remove:
        prop = RLPy.RScene.FindObject(RLPy.EObjectType_Prop, prop_name)
        if prop:
            RLPy.RScene.RemoveObject(prop) # Das offizielle iClone-Löschkommando [INDEX]
            print(f"Script Log: '{prop_name}' erfolgreich entfernt.")
            
    # iClone zwingen, den Viewport und den Scene-Manager sofort komplett neu zu zeichnen
    RLPy.RGlobal.ObjectModified(None, RLPy.EObjectModifiedType_Global)
    print("Script Log: Szene ist wieder blitzblank!")
   
def finde_projekt_im_inventory(projekt_name):
    print(f"Script Log: Suche im Inventory nach: {projekt_name}")
    
    global ic_template_path 
    
    print(f"Script Log: Nutze globalen Basis-Pfad für die Suche...")
    
    try:
        # 1. Wir gehen vom Template-Pfad einen Ordner höher (zurück zu ".../Reallusion")
        reallusion_haupt_ordner = os.path.dirname(ic_template_path)
        
        # 2. Wir steuern direkt deinen Custom-Projektordner an
        # (Standard-Struktur: Reallusion \ Custom \ iClone 8 Custom \ Project)
        projekt_basis_ordner = os.path.join(
            reallusion_haupt_ordner, 
            "Reallusion Custom", 
            "Project","IC Project"
        )
        
        # 3. Den vollen Pfad zur gewünschten .iProject-Datei zusammenbauen
        voller_datei_pfad = os.path.join(projekt_basis_ordner, f"{projekt_name}.iProject")
        
        # 4. Prüfen, ob die Datei dort wirklich existiert
        if os.path.exists(voller_datei_pfad):
            print(f"Script Log: Projekt erfolgreich gefunden: {voller_datei_pfad}")
            return voller_datei_pfad
        else:
            print(f"Script Log: Datei nicht gefunden unter: {voller_datei_pfad}")
            return None
            
    except Exception as e:
        print(f"Script Log: Fehler bei der Pfad-Berechnung: {e}")
        return None
       
def alles_neu():
   
    finde_projekt_im_inventory("waves")
    projekt_pfad = finde_projekt_im_inventory("waves")

    # 2. Nur wenn der Pfad gültig ist, laden wir die Szene scharf
    if projekt_pfad:
        print(f"Script Log: LADE PROJEKT JETZT SCHARF: {projekt_pfad}")
    
    # Stresstest-Sicherung: Timeline stoppen und auf 0 setzen
    if RLPy.RGlobal.IsPlaying():
        RLPy.RGlobal.Stop()
    RLPy.RGlobal.SetTime(RLPy.RTime.FromValue(0))
    
    # DER ECHTE LADEBEFEHL FÜR ICLONE 8:
    RLPy.RFileIO.LoadFile(projekt_pfad)
    

alles_neu()


class HauptSteuerungWindow(QtWidgets.QWidget):
    def __init__(self):
        super().__init__()
        
        layout = QtWidgets.QVBoxLayout()
        self.setLayout(layout)
        
        layout.addWidget(QtWidgets.QLabel("<b>upper Water</b>"))
        
        # Das Dropdown für deine Wasser-Presets
        self.dropdown_wasser = QtWidgets.QComboBox()
        self.dropdown_wasser.addItem("Water 0: Stormy Ocean")
        self.dropdown_wasser.addItem("Water 1: Stormy Ocean 2")
        self.dropdown_wasser.addItem("Water 2: Calm Sea")
        self.dropdown_wasser.addItem("Water 3: Foamy Waves")
        self.dropdown_wasser.addItem("Water 4: High Waves")    
        self.dropdown_wasser.addItem("Water 5: Light Waves")   
        self.dropdown_wasser.addItem("Water 6: Wave Crests")   
        self.dropdown_wasser.addItem("Water 7: Choppy Sea")    
        # Connects the click directly to your function (using the lambda trick for VS)
        self.dropdown_wasser.currentIndexChanged.connect(lambda index: waterini(index))
        layout.addWidget(self.dropdown_wasser)
        # Verbindet den Klick direkt mit deiner Funktion (Verwendet den Lambda-Trick für VS)
        self.dropdown_wasser.currentIndexChanged.connect(lambda index: waterini(index))
        layout.addWidget(self.dropdown_wasser)

        layout.addWidget(QtWidgets.QLabel("<b>under Water</b>"))
        self.dropdown_wasser2 = QtWidgets.QComboBox()
        self.dropdown_wasser2.addItem("Water 0: Stormy Ocean")
        self.dropdown_wasser2.addItem("Water 1: Stormy Ocean 2")
        self.dropdown_wasser2.addItem("Water 2: Calm Sea")
        self.dropdown_wasser2.addItem("Water 3: Foamy Waves")
        self.dropdown_wasser2.addItem("Water 4: High Waves")    
        self.dropdown_wasser2.addItem("Water 5: Light Waves")   
        self.dropdown_wasser2.addItem("Water 6: Wave Crests")   
        self.dropdown_wasser2.addItem("Water 7: Choppy Sea")     

        # Verbindet den Klick direkt mit deiner Funktion (Verwendet den Lambda-Trick für VS)
        self.dropdown_wasser2.currentIndexChanged.connect(lambda index: waterini2(index))
        layout.addWidget(self.dropdown_wasser2)

        layout.addWidget(QtWidgets.QLabel("<b>Sky</b>"))
        self.dropdown_sky = QtWidgets.QComboBox()
        self.dropdown_sky.addItem("Sky 0: Sky_01")
        self.dropdown_sky.addItem("Sky 1: Night")
        self.dropdown_sky.addItem("Sky 1: Sky_03")
        self.dropdown_sky.addItem("Sky 3: Cloudy")
        self.dropdown_sky.addItem("Sky 4: Red Sky")
        self.dropdown_sky.addItem("Sky 5: Sunset")  

        # Verbindet den Klick direkt mit deiner Funktion 
        self.dropdown_sky.currentIndexChanged.connect(lambda index: skyini(index))
        layout.addWidget(self.dropdown_sky)

        layout.addWidget(QtWidgets.QLabel("Scale"))

        # Scale Slider und SpinBox erstellen __________________________________________________________________________
        self.slider_scale = QtWidgets.QSlider(orientation=Qt.Horizontal, singleStep=1, maximum=1000, minimum=1)
        self.double_spinBox_scale = QtWidgets.QDoubleSpinBox(singleStep=1, decimals=2, maximum=1000, minimum=1)

        # Startwert setzen (scalewa * 100)
        self.slider_scale.setValue(int(scalewa * 100))
        self.double_spinBox_scale.setValue(scalewa * 100)

        # Die beiden Elemente synchronisieren
        self.double_spinBox_scale.valueChanged.connect(lambda val: self.slider_scale.setValue(int(val)))
        self.slider_scale.valueChanged.connect(lambda val: self.double_spinBox_scale.setValue(val))
        self.slider_scale.valueChanged.connect(lambda val: update_scale_high("Scale", val))

        layout.addWidget(self.slider_scale)
        layout.addWidget(self.double_spinBox_scale)

  

        # alles wird gelöscht__________________________________________________________________________________________
        self.btn_clear = QtWidgets.QPushButton("Clear All Script Props")

        # Verbindet den Klick direkt mit der Lösch-Funktion da oben (Lambda-Trick für VS)
        self.btn_clear.clicked.connect(lambda: clear_script_props())
        layout.addWidget(self.btn_clear)

        #alles neu__________________________________________________________________________________________________
        self.btn_neu = QtWidgets.QPushButton("Load All Script Props")
        # Verbindet den Klick direkt mit der Lösch-Funktion da oben (Lambda-Trick für VS)
        self.btn_neu.clicked.connect(lambda: alles_neu())
        layout.addWidget(self.btn_neu)



    def closeEvent(self, event):
        # Wir erzwingen die Ausgabe ganz am Anfang, bevor Qt aufräumt
        print("--------------------------------------------------")
        print("Speicher wird befreit!")
        print("--------------------------------------------------")
        # Wir kappen beim manuellen Schließen nur kurz die Verbindung
        try:
            self.dropdown_wasser.currentIndexChanged.disconnect()
        except:
            pass
        try:
            self.dropdown_wasser2.currentIndexChanged.disconnect()
        except:
            pass
        try:
            self.dropdown_sky.currentIndexChanged.disconnect()
        except:
            pass
        self.setParent(None)
        self.deleteLater()
        event.accept()

# ------------------------------------------------------------------
# Startblock: Alte Qt-Boxen vor dem Neustart entfernen
# ------------------------------------------------------------------
for widget in QtWidgets.QApplication.topLevelWidgets():
    if isinstance(widget, QtWidgets.QDockWidget) and widget.windowTitle() == "Wave Control Main":
        widget.close()
        widget.deleteLater()

# Neue Box erstellen
dockable_window = RLPy.RUi.CreateRDockWidget()
dockable_window.SetWindowTitle("Wave Control Main")

dock = wrapInstance(int(dockable_window.GetWindow()), QtWidgets.QDockWidget)
mein_steuer_pult = HauptSteuerungWindow()
dock.setWidget(mein_steuer_pult)
dock.setFixedWidth(280)

dockable_window.Show()
