; =============================================================================
; Dataflor X31 -> California X31 Konverter  (Version 4.0)
; =============================================================================
; ALLE BUGS BEHOBEN:
;
;  Bug 1 (NEU GEFUNDEN): Backreference-Kollision in StringRegExpReplace
;    "$1" & "2026-03-20" & "$2" → AutoIt konkateniert zu "$12026-03-20$2"
;    AutoIt liest "$12" als Capture-Gruppe 12 (nicht Gruppe 1 + "2026")!
;    Gleich bei Zeit: "$10" = Gruppe 10, bei GUID wenn sie mit Ziffer beginnt.
;    FIX: "${1}" statt "$1" → AutoIt erkennt Gruppe 1 eindeutig.
;
;  Bug 2: <QtyDetermInfo> Tag zerstört durch Anführungszeichen im Regex
;    FIX: Chr(34) statt " im Regex-Muster.
;
;  Bug 3: Kommentar-Regex "<!--[^>]*-->" bricht am > in URLs ab
;    FIX: "<!--[\s\S]*?-->" matcht alles bis zum echten Kommentarende.
;    ZUSATZ: In AutoIt muss das als separate Variable gebaut werden.
;
;  Bug 4: <Street> mit rohem Zeilenumbruch → ungültiges XML
;    FIX: StringRegExp Modus 1 + StringReplace (ohne Callback-Funktion).
;
;  Bug 5: Doppelte #include und Global-Deklarationen
;    FIX: Bereinigt.
; =============================================================================

#include <Array.au3>

Global $g_iIdCounter = 1000001

; =============================================================================
; EINSTIEGSPUNKT
; =============================================================================
If $CmdLine[0] >= 1 Then
    Local $sEin = $CmdLine[1]
    Local $sAus
    If $CmdLine[0] >= 2 Then
        $sAus = $CmdLine[2]
    Else
        $sAus = StringRegExpReplace($sEin, "(?i)\.x31$", "_California.x31")
        If $sAus = $sEin Then $sAus &= "_California.x31"
    EndIf
    Local $sPrjName = ""
    Local $sPrjID   = ""
    Local $sOwner   = ""
    If $CmdLine[0] >= 3 Then $sPrjName = $CmdLine[3]
    If $CmdLine[0] >= 4 Then $sPrjID   = $CmdLine[4]
    If $CmdLine[0] >= 5 Then $sOwner   = $CmdLine[5]
    Dataflor_Zu_California($sEin, $sAus, $sPrjName, $sPrjID, $sOwner)
Else
    Konvertieren_Dialog()
EndIf

; =============================================================================
; HAUPTFUNKTION
; =============================================================================
Func Dataflor_Zu_California($sEingabeDatei, $sAusgabeDatei, $sRefPrjName = "", $sRefPrjID = "", $sOwnerName = "")

    If Not FileExists($sEingabeDatei) Then
        MsgBox(16, "Fehler", "Eingabedatei nicht gefunden:" & @CRLF & $sEingabeDatei)
        Return False
    EndIf

    Local $hLesen = FileOpen($sEingabeDatei, 0)
    If $hLesen = -1 Then
        MsgBox(16, "Fehler", "Konnte Datei nicht oeffnen:" & @CRLF & $sEingabeDatei)
        Return False
    EndIf
    Local $sInhalt = FileRead($hLesen)
    FileClose($hLesen)

    ; BOM entfernen falls vorhanden
    If StringLeft($sInhalt, 3) = Chr(0xEF) & Chr(0xBB) & Chr(0xBF) Then
        $sInhalt = StringMid($sInhalt, 4)
    EndIf

    $g_iIdCounter = 1000001

    ; =========================================================================
    ; SCHRITT 1: Zeilenenden normalisieren → einheitlich CRLF
    ; Muss als ERSTES passieren - Dataflor hat gemischte CR/CRLF!
    ; =========================================================================
    $sInhalt = StringReplace($sInhalt, @CRLF, @LF)
    $sInhalt = StringReplace($sInhalt, @CR,   @LF)
    $sInhalt = StringReplace($sInhalt, @LF,   @CRLF)

    ; =========================================================================
    ; SCHRITT 2: <Street>-Zeilenumbrüche zu &#10; konvertieren
    ; Dataflor schreibt mehrzeilige Adressen direkt in den Tag → kaputtes XML
    ; StringRegExpReplace kann keine Callback-Funktion → manuell extrahieren
    ; =========================================================================
    Local $aStreet = StringRegExp($sInhalt, "(<Street>)([\s\S]*?)(</Street>)", 1)
    If Not @error Then
        Local $sStreetAlt = $aStreet[0] & $aStreet[1] & $aStreet[2]
        Local $sStreetNeu = $aStreet[0] & StringReplace($aStreet[1], @CRLF, "&#10;") & $aStreet[2]
        $sInhalt = StringReplace($sInhalt, $sStreetAlt, $sStreetNeu)
    EndIf

    ; =========================================================================
    ; SCHRITT 3: XML-Kommentar ersetzen
    ; BUG-FIX: "<!--[^>]*-->" bricht am > in der URL "gaeb-toolbox.de/" ab!
    ; Korrekt: Muster als Variable bauen → [\s\S]*? matcht alles inkl. >
    ; =========================================================================
    Local $sKommentarMuster = "<!--[\s\S]*?-->"
    $sInhalt = StringRegExpReplace($sInhalt, $sKommentarMuster, "<!-- REB 23.003 (2009) - X31 Export AutoIt v5 -->")

    ; =========================================================================
    ; SCHRITT 4: GAEBInfo - Datum, Zeit, Programm
    ;
    ; BUG-FIX: Backreference-Kollision!
    ; "$1" & "2026-03-20" & "$2" ergibt nach Konkatenation "$12026-03-20$2"
    ; AutoIt liest "$12" als Capture-Gruppe 12 → nicht vorhanden → leer!
    ; Gleiches bei Zeit: "$108:00:00" → Gruppe 10!
    ; FIX: "${1}" begrenzt die Gruppe eindeutig → kein Kollisionsproblem.
    ; =========================================================================
    Local $sHeute = @YEAR & "-" & StringFormat("%02d", @MON) & "-" & StringFormat("%02d", @MDAY)
    Local $sZeit  = StringFormat("%02d", @HOUR) & ":" & StringFormat("%02d", @MIN) & ":" & StringFormat("%02d", @SEC)

    $sInhalt = StringRegExpReplace($sInhalt, "(<Date>)[^<]*(</Date>)",             "${1}" & $sHeute & "${2}")
    $sInhalt = StringRegExpReplace($sInhalt, "(<Time>)[^<]*(</Time>)",             "${1}" & $sZeit  & "${2}")
    $sInhalt = StringRegExpReplace($sInhalt, "(<ProgSystem>)[^<]*(</ProgSystem>)", "${1}AutoIt REB Engine V1.2${2}")
    $sInhalt = StringRegExpReplace($sInhalt, "(<ProgName>)[^<]*(</ProgName>)",     "${1}AutoIt REB X31 Export${2}")

    ; =========================================================================
    ; SCHRITT 5: PrjInfo anpassen (nur wenn Parameter übergeben)
    ; =========================================================================
    If $sRefPrjName <> "" Then
        $sInhalt = StringRegExpReplace($sInhalt, "(<RefPrjName>)[^<]*(</RefPrjName>)", "${1}" & $sRefPrjName & "${2}")
    EndIf
    If $sRefPrjID <> "" Then
        $sInhalt = StringRegExpReplace($sInhalt, "(<RefPrjID>)[^<]*(</RefPrjID>)", "${1}" & $sRefPrjID & "${2}")
    EndIf

    ; =========================================================================
    ; SCHRITT 6: QtyDetermInfo GUID ersetzen
    ;
    ; BUG-FIX 1: Chr(34) statt " im Regex → kein String-Terminierungs-Problem
    ; BUG-FIX 2: ${1} und ${2} statt $1/$2 → GUID kann mit Ziffer beginnen!
    ;   Beispiel: GUID "3a4b..." → "$13a4b..." → AutoIt liest "$13" = Gruppe 13!
    ; =========================================================================
    Local $sNeueGUID = _GenerateGUID()
    Local $sChr34 = Chr(34)
    Local $sGUIDMuster = "(QtyDetermInfo\s+ID=" & $sChr34 & ")[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}(" & $sChr34 & ")"
    $sInhalt = StringRegExpReplace($sInhalt, $sGUIDMuster, "${1}" & $sNeueGUID & "${2}")

    ; =========================================================================
    ; SCHRITT 7: OWN-Name anpassen (optional)
    ; =========================================================================
    If $sOwnerName <> "" Then
        Local $aOWN = StringRegExp($sInhalt, "(<OWN>[\s\S]*?</OWN>)", 1)
        If Not @error Then
            Local $sOWNAlt = $aOWN[0]
            Local $sOWNNeu = StringRegExpReplace($sOWNAlt, "(<Name1>)[^<]*(</Name1>)", "${1}" & $sOwnerName & "${2}")
            $sInhalt = StringReplace($sInhalt, $sOWNAlt, $sOWNNeu)
        EndIf
    EndIf

    ; =========================================================================
    ; SCHRITT 8: CtlgAssignType entfernen
    ; =========================================================================
    $sInhalt = StringRegExpReplace($sInhalt, "\s*<CtlgAssignType\s*/>", "")
    $sInhalt = StringRegExpReplace($sInhalt, "\s*<CtlgAssignType>[^<]*</CtlgAssignType>", "")

    ; =========================================================================
    ; SCHRITT 9: BVBS:Explanation entfernen
    ; =========================================================================
    $sInhalt = StringRegExpReplace($sInhalt, "<BVBS:Explanation>[^<]*</BVBS:Explanation>", "")

    ; =========================================================================
    ; SCHRITT 10: Leere Items entfernen (Qty=0.000 ohne Mengennachweis)
    ; =========================================================================
    Local $iMax = 30
    While $iMax > 0
        Local $sVorher = $sInhalt
        $sInhalt = StringRegExpReplace($sInhalt, _
            "<Item\s[^>]+>\s*<QtyDeterm>\s*<Qty>0[,.]000</Qty>\s*</QtyDeterm>\s*</Item>", "")
        If $sInhalt = $sVorher Then ExitLoop
        $iMax -= 1
    WEnd

    ; =========================================================================
    ; SCHRITT 11: Leere Container bereinigen
    ; =========================================================================
    $iMax = 10
    While $iMax > 0
        Local $sVorher = $sInhalt
        $sInhalt = StringRegExpReplace($sInhalt, "<Itemlist>\s*</Itemlist>", "")
        $sInhalt = StringRegExpReplace($sInhalt, "<BoQBody>\s*</BoQBody>",   "")
        $sInhalt = StringRegExpReplace($sInhalt, "<BoQCtgy[^>]*>\s*</BoQCtgy>", "")
        If $sInhalt = $sVorher Then ExitLoop
        $iMax -= 1
    WEnd

    ; =========================================================================
    ; SCHRITT 12: IDs neu nummerieren (sequenziell ab DF_1000001)
    ; =========================================================================
    $sInhalt = _NummeriereIDs($sInhalt)

    ; =========================================================================
    ; SCHRITT 13: Zeilen-IDs neu vergeben
    ; PROBLEM: Dataflor vergibt Zeilen-IDs in der ERFASSUNGSREIHENFOLGE,
    ;   nicht in der POSITIONS-Reihenfolge. California sortiert die Anzeige
    ;   nach Zeilen-ID → Positionen erscheinen durcheinander.
    ; FIX: Alle Zeilen-IDs sequenziell neu vergeben in der Reihenfolge
    ;   wie sie im XML stehen (= Reihenfolge der Positionen).
    ; =========================================================================
    $sInhalt = _RenummeriereZeilenIDs($sInhalt)

    ; =========================================================================
    ; SCHRITT 14: XML formatieren
    ; =========================================================================
    $sInhalt = _FormatXML($sInhalt)

    ; =========================================================================
    ; SCHRITT 15: Ausgabedatei mit UTF-8 BOM schreiben
    ; =========================================================================
    Local $hDatei = FileOpen($sAusgabeDatei, 2 + 128) ; 2=ueberschreiben, 128=UTF-8 BOM
    If $hDatei = -1 Then
        MsgBox(16, "Fehler", "Konnte Ausgabedatei nicht erstellen:" & @CRLF & $sAusgabeDatei)
        Return False
    EndIf
    FileWrite($hDatei, $sInhalt)
    FileClose($hDatei)

    MsgBox(64, "Fertig", "Konvertierung abgeschlossen!" & @CRLF & @CRLF & "Ausgabe:" & @CRLF & $sAusgabeDatei)
    Return True
EndFunc

; =============================================================================
; Zeilen-IDs in QTakeoff-Rows neu vergeben (sequenziell in XML-Reihenfolge)
; Verhindert falsche Reihenfolge der Positionen in California
; =============================================================================
Func _RenummeriereZeilenIDs($sXML)
    ; Zähler
    Local $iCounter = 0
    Local $iSeite   = 1000

    ; Alle QTakeoff Rows der Reihe nach verarbeiten
    ; Zeilen-ID-Format: 4 Ziffern + Buchstabe (A-Z) + Ziffer (0-9)
    ; z.B. 1000A0, 1000A1, ..., 1000A9, 1000B0, ..., 1000Z9, 1001A0, ...
    Local $iPos = 1
    While True
        ; Nächste QTakeoff Row finden
        Local $iStart = StringInStr($sXML, 'QTakeoff Row="', 0, 1, $iPos)
        If $iStart = 0 Then ExitLoop

        ; Ende der Row suchen (schließendes ")
        Local $iEnd = StringInStr($sXML, '"', 0, 1, $iStart + 14)
        If $iEnd = 0 Then ExitLoop

        ; Row-Inhalt extrahieren
        Local $sRow = StringMid($sXML, $iStart + 14, $iEnd - $iStart - 14)

        ; Alte Zeilen-ID am Ende der Row finden und ersetzen
        ; Format: ...Leerzeichen + XXXXYZ + Leerzeichen am Ende
        Local $aMatch = StringRegExp($sRow, "(\s+)(\d{4}[A-Z]\d)(\s*)$", 1)
        If Not @error Then
            ; Neue ID generieren
            Local $sLetter = Chr(Asc("A") + Mod(Int($iCounter / 10), 26))
            Local $sDigit  = Mod($iCounter, 10)
            Local $sNeueID = StringFormat("%04d", $iSeite) & $sLetter & $sDigit

            $iCounter += 1
            If $iCounter >= 260 Then ; A0-Z9 = 260 Kombinationen pro Seite
                $iSeite  += 1
                $iCounter = 0
            EndIf

            ; Alte ID durch neue ersetzen (nur in dieser Row)
            Local $sAlteID  = $aMatch[1]
            Local $sRowNeu  = StringRegExpReplace($sRow, "(\s+)\d{4}[A-Z]\d(\s*)$", "${1}" & $sNeueID & "${2}")
            Local $sAltFull = 'QTakeoff Row="' & $sRow & '"'
            Local $sNeuFull = 'QTakeoff Row="' & $sRowNeu & '"'
            $sXML = StringReplace($sXML, $sAltFull, $sNeuFull, 1) ; Nur ERSTES Vorkommen
        EndIf

        $iPos = $iStart + 14
    WEnd

    Return $sXML
EndFunc

; =============================================================================
; IDs sequenziell neu nummerieren
; =============================================================================
Func _NummeriereIDs($sXML)
    Local $aAlleIDs = StringRegExp($sXML, 'ID="(DF_[0-9]+)"', 3)
    If @error Then Return $sXML

    Local $aEindeutig[0]
    For $i = 0 To UBound($aAlleIDs) - 1
        Local $bGefunden = False
        For $j = 0 To UBound($aEindeutig) - 1
            If $aEindeutig[$j] = $aAlleIDs[$i] Then
                $bGefunden = True
                ExitLoop
            EndIf
        Next
        If Not $bGefunden Then
            ReDim $aEindeutig[UBound($aEindeutig) + 1]
            $aEindeutig[UBound($aEindeutig) - 1] = $aAlleIDs[$i]
        EndIf
    Next

    For $i = 0 To UBound($aEindeutig) - 1
        Local $sNeu = "DF_" & $g_iIdCounter
        $g_iIdCounter += 1
        $sXML = StringReplace($sXML, 'ID="' & $aEindeutig[$i] & '"', 'ID="' & $sNeu & '"')
    Next
    Return $sXML
EndFunc

; =============================================================================
; XML formatieren
; =============================================================================
Func _FormatXML($sXML)
    $sXML = StringRegExpReplace($sXML, ">\s+<", ">" & @CRLF & "<")

    $sXML = StringReplace($sXML, "<BoQBody>",  "<BoQBody>"  & @CRLF)
    $sXML = StringReplace($sXML, "<Itemlist>", "<Itemlist>" & @CRLF)
    $sXML = StringRegExpReplace($sXML, "(<BoQCtgy[^>]*>)", "${1}" & @CRLF)
    $sXML = StringRegExpReplace($sXML, "(<Item [^>]*>)",   "${1}" & @CRLF)

    Local $aClose[5] = ["</BoQBody>", "</Itemlist>", "</BoQCtgy>", "</Item>", "</QtyDeterm>"]
    For $i = 0 To 4
        $sXML = StringReplace($sXML, $aClose[$i], @CRLF & $aClose[$i] & @CRLF)
    Next

    $sXML = StringReplace($sXML, "<QtyDeterm>", @CRLF & "<QtyDeterm>" & @CRLF)

    While StringInStr($sXML, @CRLF & @CRLF & @CRLF)
        $sXML = StringReplace($sXML, @CRLF & @CRLF & @CRLF, @CRLF & @CRLF)
    WEnd

    While StringLeft($sXML, 2) = @CRLF
        $sXML = StringMid($sXML, 3)
    WEnd

    Return $sXML
EndFunc

; =============================================================================
; GUID generieren
; =============================================================================
Func _GenerateGUID()
    Local $aTeile[5] = [8, 4, 4, 4, 12]
    Local $sGUID = ""
    For $i = 0 To 4
        If $i > 0 Then $sGUID &= "-"
        For $j = 1 To $aTeile[$i]
            $sGUID &= StringLower(Hex(Random(0, 15, 1), 1))
        Next
    Next
    Return $sGUID
EndFunc

; =============================================================================
; Interaktiver Dialog
; =============================================================================
Func Konvertieren_Dialog()
    Local $sEingabe = FileOpenDialog( _
        "Dataflor X31 Datei waehlen", _
        @DesktopDir, _
        "X31 Dateien (*.x31)|Alle Dateien (*.*)", 1)
    If @error Then Return

    Local $sOrdner = StringLeft($sEingabe, StringInStr($sEingabe, "\", 0, -1) - 1)
    Local $sName   = StringMid($sEingabe, StringInStr($sEingabe, "\", 0, -1) + 1)
    $sName = StringRegExpReplace($sName, "(?i)\.x31$", "_California.x31")

    Local $sAusgabe = FileSaveDialog( _
        "Ausgabedatei speichern", $sOrdner, _
        "X31 Dateien (*.x31)", 2 + 16, $sName)
    If @error Then Return

    Local $sRefPrjName = InputBox("Projektname", _
        "RefPrjName fuer California" & @CRLF & "(leer lassen = aus Dataflor uebernehmen):", "")
    Local $sRefPrjID = InputBox("Projekt-ID", _
        "RefPrjID fuer California" & @CRLF & "(leer lassen = aus Dataflor uebernehmen):", "")
    Local $sOwner = InputBox("Aufsteller", _
        "Name des Aufstellers (OWN/Name1)" & @CRLF & "(leer lassen = unveraendert):", "")

    Dataflor_Zu_California($sEingabe, $sAusgabe, $sRefPrjName, $sRefPrjID, $sOwner)
EndFunc
