#!/usr/bin/env python3
"""
add_countdown_click.py — Add Countdown and Click AudioTracks to LinkinPark.als

Creates two new AudioTracks:
  - Countdown (Id=207): plays "4, 3, 2, 1" voice before songs
  - Click (Id=208): metronome click on every beat

For now, populates only What I've Done (scene 0, 120 BPM).
"""

import os, re, subprocess
from xml.etree import ElementTree as ET

XML_PATH = os.path.join(os.path.dirname(__file__),
                        "LinkinPark Project", "LinkinPark.xml")
PROJECT_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)),
                           "LinkinPark Project")


def get_duration(path):
    result = subprocess.run(
        ["ffprobe", "-v", "quiet", "-show_entries", "format=duration",
         "-of", "csv=p=0", path],
        capture_output=True, text=True)
    return float(result.stdout.strip().split(",")[0])


def get_file_size(path):
    return os.path.getsize(path)


def get_sample_rate(path):
    result = subprocess.run(
        ["ffprobe", "-v", "quiet", "-show_entries", "stream=sample_rate",
         "-of", "csv=p=0", path],
        capture_output=True, text=True)
    lines = [l for l in result.stdout.strip().splitlines() if l.strip().isdigit()]
    return int(lines[0]) if lines else 48000


def main():
    print(f"Reading {XML_PATH}...")
    with open(XML_PATH, "r", encoding="utf-8") as f:
        content = f.read()

    # Parse to get structural info
    root = ET.fromstring(content.encode("utf-8"))

    # NextPointeeId
    m = re.search(r'NextPointeeId Value="(\d+)"', content)
    next_id = int(m.group(1))
    original_next_id = next_id
    print(f"  NextPointeeId: {next_id}")

    # Current scene count
    scenes = root.findall('.//Scene')
    num_scenes = len(scenes)
    print(f"  Scenes: {num_scenes}")

    # ================================================================
    # Step 1: Extract AudioTrack 116 (Metronome) as template
    # ================================================================
    track_116 = root.find('.//AudioTrack[@Id="116"]')
    template_xml = ET.tostring(track_116, encoding='unicode')

    # ================================================================
    # Step 2: Create Countdown AudioTrack (Id=207)
    # ================================================================
    countdown_file = "Samples/Imported/countdown-120bpm.flac"
    countdown_abs = os.path.join(PROJECT_DIR, countdown_file)
    countdown_dur = get_duration(countdown_abs)
    countdown_size = get_file_size(countdown_abs)
    countdown_sr = get_sample_rate(countdown_abs)

    # Click file
    click_file = "Samples/Imported/click-120bpm.flac"
    click_abs = os.path.join(PROJECT_DIR, click_file)
    click_dur = get_duration(click_abs)
    click_size = get_file_size(click_abs)
    click_sr = get_sample_rate(click_abs)

    bpm = 120  # What I've Done BPM
    beat_dur = 60.0 / bpm

    countdown_beats = countdown_dur * bpm / 60.0
    click_beats = click_dur * bpm / 60.0

    print(f"  Countdown: {countdown_dur:.3f}s = {countdown_beats:.2f} beats")
    print(f"  Click: {click_dur:.3f}s = {click_beats:.2f} beats")

    # ================================================================
    # Step 3: Build new tracks using template modification
    # ================================================================
    
    # We'll create the tracks by modifying the template
    # Need to reassign ALL Id attributes to unique values
    
    def build_track(track_id, track_name, color, group_id,
                    scene_0_clip_info, num_scenes, start_next_id):
        """Build a complete AudioTrack XML string based on track 116 template.
        
        scene_0_clip_info: None for empty, or dict with:
            name, bpm, rel_path, abs_path, duration_sec, file_size, sample_rate
        """
        nid = start_next_id
        
        # Parse template fresh
        t = ET.fromstring(template_xml)
        
        # Set track Id
        t.set('Id', str(track_id))
        
        # Set name
        for tag in ['UserName', 'EffectiveName']:
            el = t.find(f'.//{tag}')
            if el is not None:
                el.set('Value', track_name)
        # Also set the Name element inside the track
        name_el = t.find('Name')
        if name_el is not None:
            for sub in name_el:
                if sub.tag in ['UserName', 'EffectiveName']:
                    sub.set('Value', track_name)
        
        # Set color
        color_el = t.find('Color')
        if color_el is not None:
            color_el.set('Value', str(color))
        
        # Set TrackGroupId
        tgid = t.find('TrackGroupId')
        if tgid is not None:
            tgid.set('Value', str(group_id))
        
        # Reassign all Id attributes to unique values
        for elem in t.iter():
            if 'Id' in elem.attrib and elem.tag != 'AudioTrack':
                # Skip certain structural Ids that should stay 0
                if elem.tag in ['RemoteableTimeSignature']:
                    continue
                old_id = elem.get('Id')
                try:
                    int(old_id)  # only reassign numeric IDs
                    elem.set('Id', str(nid))
                    nid += 1
                except ValueError:
                    pass
        
        # Now handle clip slots
        # Find MainSequencer's ClipSlotList
        dc = t.find('DeviceChain')
        ms = dc.find('MainSequencer') if dc is not None else None
        csl = ms.find('ClipSlotList') if ms is not None else None
        
        if csl is not None:
            # Remove all existing clip slots
            for slot in list(csl):
                csl.remove(slot)
            
            # Add clip slots for each scene
            for scene_idx in range(num_scenes):
                slot = ET.SubElement(csl, 'ClipSlot', Id=str(scene_idx))
                ET.SubElement(slot, 'LomId', Value='0')
                inner_cs = ET.SubElement(slot, 'ClipSlot')
                
                if scene_idx == 0 and scene_0_clip_info is not None:
                    info = scene_0_clip_info
                    beats = info['duration_sec'] * info['bpm'] / 60.0
                    bd = 60.0 / info['bpm']
                    default_dur = int(round(info['duration_sec'] * info['sample_rate']))
                    
                    value = ET.SubElement(inner_cs, 'Value')
                    clip = ET.SubElement(value, 'AudioClip', Id=str(nid), Time='0')
                    nid += 1
                    
                    ET.SubElement(clip, 'LomId', Value='0')
                    ET.SubElement(clip, 'LomIdView', Value='0')
                    ET.SubElement(clip, 'CurrentStart', Value='0')
                    ET.SubElement(clip, 'CurrentEnd', Value=f'{beats:.4f}')
                    
                    loop = ET.SubElement(clip, 'Loop')
                    ET.SubElement(loop, 'LoopStart', Value='0')
                    ET.SubElement(loop, 'LoopEnd', Value=f'{beats:.4f}')
                    ET.SubElement(loop, 'StartRelative', Value='0')
                    ET.SubElement(loop, 'LoopOn', Value='false')
                    ET.SubElement(loop, 'OutMarker', Value=f'{beats:.4f}')
                    ET.SubElement(loop, 'HiddenLoopStart', Value='0')
                    ET.SubElement(loop, 'HiddenLoopEnd', Value=f'{beats:.4f}')
                    
                    ET.SubElement(clip, 'Name', Value=info['name'])
                    ET.SubElement(clip, 'Annotation', Value='')
                    ET.SubElement(clip, 'Color', Value=str(color))
                    ET.SubElement(clip, 'LaunchMode', Value='0')
                    ET.SubElement(clip, 'LaunchQuantisation', Value='0')
                    
                    ts = ET.SubElement(clip, 'TimeSignature')
                    tss = ET.SubElement(ts, 'TimeSignatures')
                    rts = ET.SubElement(tss, 'RemoteableTimeSignature', Id='0')
                    ET.SubElement(rts, 'Numerator', Value='4')
                    ET.SubElement(rts, 'Denominator', Value='4')
                    ET.SubElement(rts, 'Time', Value='0')
                    
                    envs = ET.SubElement(clip, 'Envelopes')
                    ET.SubElement(envs, 'Envelopes')
                    
                    stp = ET.SubElement(clip, 'ScrollerTimePreserver')
                    ET.SubElement(stp, 'LeftTime', Value='0')
                    ET.SubElement(stp, 'RightTime', Value='0')
                    
                    tsel = ET.SubElement(clip, 'TimeSelection')
                    ET.SubElement(tsel, 'AnchorTime', Value='0')
                    ET.SubElement(tsel, 'OtherTime', Value='0')
                    
                    ET.SubElement(clip, 'Legato', Value='false')
                    ET.SubElement(clip, 'Ram', Value='false')
                    gs = ET.SubElement(clip, 'GrooveSettings')
                    ET.SubElement(gs, 'GrooveId', Value='-1')
                    ET.SubElement(clip, 'Disabled', Value='false')
                    ET.SubElement(clip, 'VelocityAmount', Value='0')
                    
                    fa = ET.SubElement(clip, 'FollowAction')
                    ET.SubElement(fa, 'FollowTime', Value='4')
                    ET.SubElement(fa, 'IsLinked', Value='true')
                    ET.SubElement(fa, 'LoopIterations', Value='1')
                    ET.SubElement(fa, 'FollowActionA', Value='4')
                    ET.SubElement(fa, 'FollowActionB', Value='0')
                    ET.SubElement(fa, 'FollowChanceA', Value='100')
                    ET.SubElement(fa, 'FollowChanceB', Value='0')
                    ET.SubElement(fa, 'JumpIndexA', Value='0')
                    ET.SubElement(fa, 'JumpIndexB', Value='0')
                    ET.SubElement(fa, 'FollowActionEnabled', Value='false')
                    
                    grid = ET.SubElement(clip, 'Grid')
                    ET.SubElement(grid, 'FixedNumerator', Value='1')
                    ET.SubElement(grid, 'FixedDenominator', Value='16')
                    ET.SubElement(grid, 'GridIntervalPixel', Value='20')
                    ET.SubElement(grid, 'Ntoles', Value='2')
                    ET.SubElement(grid, 'SnapToGrid', Value='true')
                    ET.SubElement(grid, 'Fixed', Value='false')
                    
                    ET.SubElement(clip, 'FreezeStart', Value='0')
                    ET.SubElement(clip, 'FreezeEnd', Value='0')
                    ET.SubElement(clip, 'IsWarped', Value='true')
                    ET.SubElement(clip, 'TakeId', Value='1')
                    ET.SubElement(clip, 'IsInKey', Value='false')
                    
                    si = ET.SubElement(clip, 'ScaleInformation')
                    ET.SubElement(si, 'Root', Value='0')
                    ET.SubElement(si, 'Name', Value='0')
                    
                    sref = ET.SubElement(clip, 'SampleRef')
                    fref = ET.SubElement(sref, 'FileRef')
                    ET.SubElement(fref, 'RelativePathType', Value='3')
                    ET.SubElement(fref, 'RelativePath', Value=info['rel_path'])
                    ET.SubElement(fref, 'Path', Value=info['abs_path'])
                    ET.SubElement(fref, 'Type', Value='1')
                    ET.SubElement(fref, 'LivePackName', Value='')
                    ET.SubElement(fref, 'LivePackId', Value='')
                    ET.SubElement(fref, 'OriginalFileSize', Value=str(info['file_size']))
                    ET.SubElement(fref, 'OriginalCrc', Value='0')
                    ET.SubElement(fref, 'SourceHint', Value='')
                    ET.SubElement(sref, 'LastModDate', Value='0')
                    ET.SubElement(sref, 'SourceContext')
                    ET.SubElement(sref, 'SampleUsageHint', Value='0')
                    ET.SubElement(sref, 'DefaultDuration', Value=str(default_dur))
                    ET.SubElement(sref, 'DefaultSampleRate', Value=str(info['sample_rate']))
                    ET.SubElement(sref, 'SamplesToAutoWarp', Value='0')
                    
                    onsets = ET.SubElement(clip, 'Onsets')
                    ET.SubElement(onsets, 'UserOnsets')
                    ET.SubElement(onsets, 'HasUserOnsets', Value='false')
                    
                    ET.SubElement(clip, 'WarpMode', Value='0')
                    ET.SubElement(clip, 'GranularityTones', Value='30')
                    ET.SubElement(clip, 'GranularityTexture', Value='65')
                    ET.SubElement(clip, 'FluctuationTexture', Value='25')
                    ET.SubElement(clip, 'TransientResolution', Value='6')
                    ET.SubElement(clip, 'TransientLoopMode', Value='2')
                    ET.SubElement(clip, 'TransientEnvelope', Value='100')
                    ET.SubElement(clip, 'ComplexProFormants', Value='100')
                    ET.SubElement(clip, 'ComplexProEnvelope', Value='128')
                    ET.SubElement(clip, 'Sync', Value='true')
                    ET.SubElement(clip, 'HiQ', Value='false')
                    ET.SubElement(clip, 'Fade', Value='true')
                    
                    fades = ET.SubElement(clip, 'Fades')
                    ET.SubElement(fades, 'FadeInLength', Value='0')
                    ET.SubElement(fades, 'FadeOutLength', Value='0')
                    ET.SubElement(fades, 'ClipFadesAreInitialized', Value='true')
                    ET.SubElement(fades, 'CrossfadeInState', Value='0')
                    ET.SubElement(fades, 'FadeInCurveSkew', Value='0')
                    ET.SubElement(fades, 'FadeInCurveSlope', Value='0')
                    ET.SubElement(fades, 'FadeOutCurveSkew', Value='0')
                    ET.SubElement(fades, 'FadeOutCurveSlope', Value='0')
                    ET.SubElement(fades, 'IsDefaultFadeIn', Value='true')
                    ET.SubElement(fades, 'IsDefaultFadeOut', Value='true')
                    
                    ET.SubElement(clip, 'PitchCoarse', Value='0')
                    ET.SubElement(clip, 'PitchFine', Value='0')
                    ET.SubElement(clip, 'SampleVolume', Value='1')
                    
                    wms = ET.SubElement(clip, 'WarpMarkers')
                    ET.SubElement(wms, 'WarpMarker', Id='0', SecTime='0', BeatTime='0')
                    ET.SubElement(wms, 'WarpMarker', Id='1', SecTime=f'{bd:.10f}', BeatTime='1')
                    ET.SubElement(clip, 'SavedWarpMarkersForStretched')
                    ET.SubElement(clip, 'MarkersGenerated', Value='false')
                    ET.SubElement(clip, 'IsSongTempoLeader', Value='false')
                else:
                    ET.SubElement(inner_cs, 'Value')
                
                ET.SubElement(slot, 'HasStop', Value='true')
                ET.SubElement(slot, 'NeedRefreeze', Value='true')
        
        # Also handle FreezeSequencer ClipSlotList
        fs = dc.find('FreezeSequencer') if dc is not None else None
        if fs is not None:
            fs_csl = fs.find('ClipSlotList')
            if fs_csl is not None:
                for slot in list(fs_csl):
                    fs_csl.remove(slot)
                for scene_idx in range(num_scenes):
                    slot = ET.SubElement(fs_csl, 'ClipSlot', Id=str(scene_idx))
                    ET.SubElement(slot, 'LomId', Value='0')
                    inner = ET.SubElement(slot, 'ClipSlot')
                    ET.SubElement(inner, 'Value')
                    ET.SubElement(slot, 'HasStop', Value='true')
                    ET.SubElement(slot, 'NeedRefreeze', Value='true')
        
        # Remove all existing AudioClips from template (they belong to other songs)
        # Already handled by rebuilding clip slots above
        
        return t, nid

    # Build Countdown track
    print("\nBuilding Countdown track (Id=207)...")
    countdown_track, next_id = build_track(
        track_id=207,
        track_name="Countdown",
        color=13,  # purple
        group_id=-1,  # no group (independent track)
        scene_0_clip_info={
            'name': 'countdown',
            'bpm': bpm,
            'rel_path': countdown_file,
            'abs_path': countdown_abs,
            'duration_sec': countdown_dur,
            'file_size': countdown_size,
            'sample_rate': countdown_sr,
        },
        num_scenes=num_scenes,
        start_next_id=next_id,
    )
    print(f"  IDs allocated: {original_next_id} -> {next_id}")

    # Build Click track
    print("Building Click track (Id=208)...")
    click_track, next_id = build_track(
        track_id=208,
        track_name="Click",
        color=19,  # light grey
        group_id=-1,
        scene_0_clip_info={
            'name': 'click',
            'bpm': bpm,
            'rel_path': click_file,
            'abs_path': click_abs,
            'duration_sec': click_dur,
            'file_size': click_size,
            'sample_rate': click_sr,
        },
        num_scenes=num_scenes,
        start_next_id=next_id,
    )
    print(f"  IDs allocated up to: {next_id}")

    # ================================================================
    # Step 4: Insert tracks into XML using string manipulation
    # ================================================================
    
    # Convert new tracks to XML strings
    countdown_xml = ET.tostring(countdown_track, encoding='unicode')
    click_xml = ET.tostring(click_track, encoding='unicode')

    # Insert after the last AudioTrack (Guitar, Id=206) but before ReturnTracks
    # Find the position of the first ReturnTrack
    insert_marker = '<ReturnTrack Id='
    insert_pos = content.find(insert_marker)
    if insert_pos == -1:
        print("ERROR: Could not find ReturnTrack insertion point")
        return
    
    # Go back to find the start of the line for proper indentation
    line_start = content.rfind('\n', 0, insert_pos) + 1
    indent = content[line_start:insert_pos]
    
    insertion = f"{indent}{countdown_xml}\n{indent}{click_xml}\n"
    content = content[:insert_pos] + insertion + content[insert_pos:]
    
    # ================================================================
    # Step 5: Update NextPointeeId
    # ================================================================
    content = content.replace(
        f'NextPointeeId Value="{original_next_id}"',
        f'NextPointeeId Value="{next_id}"')

    # ================================================================
    # Step 6: Validate & write
    # ================================================================
    print("\nValidating XML...")
    try:
        ET.fromstring(content.encode("utf-8"))
        print("  XML is valid!")
    except ET.ParseError as e:
        print(f"  XML ERROR: {e}")
        return

    print(f"Writing {XML_PATH}...")
    with open(XML_PATH, "w", encoding="utf-8") as f:
        f.write(content)
    
    print(f"Done! NextPointeeId now {next_id}")
    print(f"Added: Countdown (Id=207) and Click (Id=208)")
    print("Run ./build.sh to create LinkinPark.als for Ableton.")


if __name__ == "__main__":
    main()
