#!/usr/bin/env python
from stepfever import *
import time
import struct

SUPRESS_PRINT = False

class NoteType:
        Null                 = 0
        Tap                  = 1
        HoldHead             = 2
        HoldBody             = 3
        HoldTail             = 4
        RollHead             = 5
        Item                 = 6
        Fake                 = 7
        HoldHeadFake         = 8
        HoldBodyFake         = 9
        HoldTailFake         = 10
        RollHeadFake         = 11
        ItemFake             = 12

class NX10:

    Null                    =   0x00
    Fake                    =   0x73   #  0b00100011

    Tap                     =   0xB3   #  0b01000011
    HoldHead                =   0xB4   #  0b01010111
    HoldBody                =   0xB6   #  0b01011011
    HoldTail                =   0xB7   #  0b01011111

    HoldHeadFake            =   0x74   #  0b00110111
    HoldBodyFake            =   0x76   #  0b00111011
    HoldTailFake            =   0x77   #  0b00111111
    Item                    =   0xF1   #  0b01100001


class NX20:
    
    Null                    =   0x00

    Effect                  =   0x41   #  0b01000001
    DivBrain                =   0x42   #  0b01000010
    Fake                    =   0x23   #  0b00100011
    Tap                     =   0x43   #  0b01000011
    HoldHeadFake            =   0x37   #  0b00110111
    HoldHead                =   0x57   #  0b01010111
    HoldBodyFake            =   0x3b   #  0b00111011
    HoldBody                =   0x5B   #  0b01011011
    HoldTailFake            =   0x3f   #  0b00111111
    HoldTail                =   0x5F   #  0b01011111
    FakeItem                =   0x21   #  0b00100001
    Item                    =   0x61   #  0b01100001
    Row                     =   0x80   #  0b10000000
    
    MetaMissionLevel        =   1000
    MetaChartLevel          =   1001

    NoteSeedAction          =   0
    NoteSeedShield          =   1
    NoteSeedChange          =   2
    NoteSeedAcceleration    =   3
    NoteSeedFlash           =   4
    NoteSeedMineTap         =   5
    NoteSeedMineHold        =   6
    NoteSeedAttack          =   7
    NoteSeedDrain           =   8
    NoteSeedHeart           =   9
    NoteSeedSpeed2          =   10
    NoteSeedRandom          =   11
    NoteSeedSpeed3          =   12
    NoteSeedSpeed4          =   13
    NoteSeedSpeed8          =   14
    NoteSeedSpeed1          =   15
    NoteSeedPotion          =   16 
    NoteSeedRotate0         =   17
    NoteSeedRotate90        =   18
    NoteSeedRotate180       =   19
    NoteSeedRotate270       =   20
    NoteSeedSpeed_          =   21
    NoteSeedBomb            =   22 
    NoteSeedHyperPotion     =   23

NOTE_NX202SFC   =   {
                        NX20.Null           :   NoteType.Null,
                        NX20.Fake           :   NoteType.Fake,
                        NX20.Tap            :   NoteType.Tap,
                        NX20.HoldHead       :   NoteType.HoldHead,
                        NX20.HoldHeadFake   :   NoteType.HoldHeadFake,
                        NX20.HoldBody       :   NoteType.HoldBody,
                        NX20.HoldBodyFake   :   NoteType.HoldBodyFake,
                        NX20.HoldTail       :   NoteType.HoldTail,
                        NX20.FakeItem       :   NoteType.ItemFake,
                        NX20.Item           :   NoteType.Item
                     }

NOTE_NX102SFC   =   {
                        NX10.Null           :   NoteType.Null,
                        NX10.Fake           :   NoteType.Fake,
                        NX10.Tap            :   NoteType.Tap,
                        NX10.HoldHead       :   NoteType.HoldHead,
                        NX10.HoldHeadFake   :   NoteType.HoldHeadFake,
                        NX10.HoldBody       :   NoteType.HoldBody,
                        NX10.HoldBodyFake   :   NoteType.HoldBodyFake,
                        NX10.HoldTail       :   NoteType.HoldTail,
                        NX10.Item           :   NoteType.Item
                     }

MODE_NX202SFC   =   {
                     5  :   ChartType.PUMP_SINGLE,
                     10 :   ChartType.PUMP_DOUBLE
                     }

def ParseStep(stepdata):
    if not stepdata[0] in NOTE_NX202SFC or stepdata[0] == NX20.Effect:
        return {"type":NoteType.Null,"seed": 0,"skin":0,"trigger":0}
    else:
        return {"type":NOTE_NX202SFC[stepdata[0]],"seed": stepdata[1],"skin":stepdata[2],"trigger":0}


def ParseStepNX10(stepdata):
    if not stepdata[0] in NOTE_NX102SFC:
        return {"type":NoteType.Null,"seed": 0,"skin":0,"trigger":0}
    else:
        return {"type":NOTE_NX102SFC[stepdata[0]],"seed": stepdata[1],"skin":stepdata[2],"trigger":0}

def GetChartData(file):
    out = {"level":-1,"type":ChartType.PUMP_SINGLE,"color" : (255,255,255),"added":time.time(), "title":"Converted by Tool","author":"nx2sfm", "file" : file}
    f = open(file,"rb")
    head = f.read(20)
    out["nxtype"] = head[:4]
    if head[:4] == "NX20":
        startcolumn =   struct.unpack("I", head[4:8])[0]
        numcolumns  =   struct.unpack("I", head[8:12])[0]
        lightmap    =   struct.unpack("I", head[12:16])[0]
        metablocks  =   struct.unpack("I", head[16:20])[0]
        out["type"] =   MODE_NX202SFC[numcolumns]
        for i in range(metablocks):
            data    =   f.read(8)
            idx     =   struct.unpack("I", data[:4])[0]
            value   =   struct.unpack("I", data[4:8])[0]
            if idx == NX20.MetaChartLevel:
                out["level"] = value
                break
    elif head[:4] == "NX10":
        nxversion   =   struct.unpack("4s", head[0:4])[0]
        startcolumn =   struct.unpack("I", head[4:8])[0]
        numcolumns  =   struct.unpack("I", head[8:12])[0]
        splits      =   struct.unpack("I", head[12:16])[0]
        lightmap    =   startcolumn > 9
        out["type"] =   MODE_NX202SFC[numcolumns]
    else:
        print "NXData::GetChartData() - Invalid file magic: %s" %head[:4]
    f.close()
    return out

def ParseChartNX20(chart):
    print "Parsing NX20 - %s" %chart["file"]
    f = open(chart["file"], "rb")
    mode = chart["type"]
    startbpm = -1
    bpmchanges = []
    rushchanges = []
    sfchanges = []
    sschanges = []
    effects = []
    tcchanges = []
    warps = []
    bgchanges = []
    rows = []
    lastbpm = -1
    
    beat = 0
    time = 0
    head        =   f.read(20)
    nxversion   =   struct.unpack("4s", head[0:4])[0]
    startcolumn =   struct.unpack("I", head[4:8])[0]
    numcolumns  =   struct.unpack("I", head[8:12])[0]
    lightmap    =   struct.unpack("I", head[12:16])[0]
    metablocks  =   struct.unpack("I", head[16:20])[0]
    
    #    Lets just skip the metadatablocks for now:
    junk = f.read(8*metablocks)
    numsplits = struct.unpack("I", f.read(4))[0]
    lastsf = None
    lastss = None
    
    if not SUPRESS_PRINT:
        print "Number of splits: %s" %numsplits
    for s in range(numsplits):
        systemselected  =   struct.unpack("I", f.read(4))[0]
        metablocks      =   struct.unpack("I", f.read(4))[0]
        
        #   We will just skip the metadatablocks from split:
        junk = f.read(8*metablocks)
        
        numblocks       =   struct.unpack("I", f.read(4))[0]
        if not SUPRESS_PRINT:
            print "    System Selected: %s" %systemselected
            print "    Number of blocks: %s" %numblocks
        #    Currently, I dont know how System Selected works, but when System Selected != 0, the machine seens to pick a random block.
        #    This converter is just for test, so we will pick the random block here on convert
        
        SelectedBlock = 0 if systemselected == 0 else ((int)(random()*numblocks))
        if not SUPRESS_PRINT:
            print "    Block Picked: %s" %SelectedBlock
        
        for blk in range(0,numblocks):
            steptime        =   struct.unpack("f", f.read(4))[0]
            bpm             =   struct.unpack("f", f.read(4))[0]
            mysteryblock    =   struct.unpack("f", f.read(4))[0]
            delay           =   struct.unpack("f", f.read(4))[0]
            speed           =   struct.unpack("f", f.read(4))[0]
            beatsplit       =   struct.unpack("B", f.read(1))[0]
            beatmeasure     =   struct.unpack("B", f.read(1))[0]
            smoothspeed     =   struct.unpack("B", f.read(1))[0]
            unknownflag     =   struct.unpack("B", f.read(1))[0]
            divisionconds   =   struct.unpack("I", f.read(4))[0]
            freeze          =   (speed<0);
            bps             =   bpm / 60.0
            
            startbpm        =   bpm if startbpm == -1 else startbpm
            
            #    We will skip division conditions too
            junk            =   f.read(8*divisionconds)
            
            numrows         =   struct.unpack("I", f.read(4))[0]
            if SelectedBlock != blk:
                if not SUPRESS_PRINT:
                    print "    Skipping not selected block %s" %blk
                for n in range(numrows):
                    rowt    =   struct.unpack("BBBB", f.read(4))[0]
                    if rowt[0] != NX20.Row:
                        junk = f.read(4*numcolumns)
                        
                        
            else:
                    speed   =   abs(speed)
                    if not SUPRESS_PRINT:
                        print   "    CurrentTime: %s" %time
                        print   "    CurrentBeat: %s" %beat
                        print   "    Block(%s)" % blk
                        print   "        StepTime:               %s" %steptime
                        print   "        BPM:                    %s" %bpm
                        print   "        Mystery Block:          %s" %mysteryblock
                        print   "        Delay:                  %s" %delay
                        print   "        Freeze:                 %s"%freeze
                        print   "        Speed:                  %s"%speed
                        print   "        Beat Split:             %s"%beatsplit
                        print   "        Beat Measure:           %s"%beatmeasure
                        print   "        Smooth Speed:           %s"%smoothspeed
                        print   "        Unknown Flag:           %s"%unknownflag
                        print   "        Division Conditions:    %s" %divisionconds
                        print   "        Number of Rows:         %s" %numrows
                    
                    #    BPM Changes
                    if lastbpm != bpm:
                        bpmchanges.append({"time": steptime/1000, "bpm" : bpm})
                        lastbpm = bpm
                    delay = delay / 1000
     
                    #    Delays
                    beat += delay * bps
                    time += delay
                    
                    #    ScrollFactor
                    sf  =   mysteryblock * beatsplit
                    lastsf = {"beat" : beat, "factor" : sf, "smooth" : smoothspeed>0, "smoothtime" : steptime}
                    if lastsf != None:
                        lastsf["smoothtime"] = (steptime - lastsf["smoothtime"]) / 1000
                        sfchanges.append(lastsf)
                        if not SUPRESS_PRINT:
                            print "        Adding Scrollfactor Change - Factor: %s Smooth: %s Beat: %s SmoothTime: %s" %(lastsf["factor"],lastsf["smooth"],lastsf["beat"],lastsf["smoothtime"])
                    
                    lastsf = {"beat" : beat, "factor" : sf, "smooth" : smoothspeed>0, "smoothtime" : steptime}

                    if lastss != None:
                        lastss["smoothtime"] = (steptime - lastss["smoothtime"]) / 1000
                        sschanges.append(lastss)
                        if not SUPRESS_PRINT:
                            print "        Adding ScrollSpeed Change - Factor: %s Smooth: %s Beat: %s SmoothTime: %s" %(lastss["factor"],lastss["smooth"],lastss["beat"],lastss["smoothtime"])
                    
                    lastss = {"beat" : beat, "factor" : speed, "smooth" : smoothspeed>0, "smoothtime" : steptime}

                    if s == numsplits-1:
                        if not SUPRESS_PRINT:
                            print "        Adding last ss"
                        sschanges.append(lastss)

                    #    Check for warps
                    if round(steptime*1000) != round(time*1000*1000):
                        # Lets do an warp!
                        WarpBeat = ((steptime/1000) - time ) * bps + beat;
                        if not SUPRESS_PRINT:
                            print "        Time Warp from %s ms to %s ms from beat %s to beat %s" %(steptime,time*1000,WarpBeat,beat)
                        #warps.append({"beatFrom":beat,"beatTo":WarpBeat})
                        warps.append({"time":(steptime/1000),"beatTo":beat})
                        
                    #    Process Rows
                    for n in range(numrows):
                        notes = []
                        rowt = struct.unpack("BBBB",f.read(4))
                        if rowt[0] != NX20.Row:     #    If its not an empty row
                            notedata = ParseStep(rowt)
                            notedata["beat"] = beat
                            notes.append(notedata)
                            for k in range(numcolumns-1):
                                rowt = struct.unpack("BBBB",f.read(4))
                                notedata = ParseStep(rowt)
                                notedata["beat"] = beat
                                notes.append(notedata)
                        if len(notes) > 0:
                            rows.append({"beat":beat, "notes":notes})
                        beat += 1.0 / beatsplit
                        time += 1.0 / (beatsplit * bps)
    f.close()
    data = GenSFC(chart["id"], chart["level"], chart["type"], startbpm, chart["title"], chart["author"], bpmchanges, warps, rushchanges, sfchanges, sschanges, tcchanges, effects, bgchanges, rows)
    return data

def ParseChartNX10(chart):
    print "Parsing NX10 %s" %chart["file"]
    f = open(chart["file"], "rb")
    mode = chart["type"]

    startbpm = -1
    bpmchanges = []
    rushchanges = []
    sfchanges = []
    sschanges = []
    effects = []
    tcchanges = []
    warps = []
    bgchanges = []
    rows = []
    lastbpm = -1
    beat = 0
    time = 0


    head = f.read(16)
    nxversion   =   struct.unpack("4s", head[0:4])[0]
    startcolumn =   struct.unpack("I", head[4:8])[0]
    numcolumns  =   struct.unpack("I", head[8:12])[0]
    numsplits   =   struct.unpack("I", head[12:16])[0]
    lightmap    =   startcolumn > 9

    lastsf = None
    lastss = None

    if not SUPRESS_PRINT:
        print "Format: %s - Start Column %d - Num Columns: %d - Splits: %d" %(nxversion,startcolumn,numcolumns,numsplits)
    splitsd = f.read(4*numsplits)
    splitsd = struct.unpack("%sI"%numsplits,splitsd)

    for s in range(numsplits):
        file_pos        =   splitsd[s]
        f.seek(file_pos)
        blocks          =   struct.unpack("I", f.read(4))[0]
        if not SUPRESS_PRINT:
            print "\tNumber of blocks %s" %blocks
        blocksd         =   struct.unpack("%sI"%blocks, f.read(4*blocks))
        for z in range(blocks):
            f.seek(blocksd[z])
            steptime        =   struct.unpack("f", f.read(4))[0]
            bpm             =   struct.unpack("f", f.read(4))[0]
            mysteryblock    =   struct.unpack("f", f.read(4))[0]
            delay           =   struct.unpack("f", f.read(4))[0]
            speed           =   struct.unpack("f", f.read(4))[0]
            divisionconds   =   struct.unpack("I", f.read(4))[0]
            beatsplit       =   struct.unpack("H", f.read(2))[0]
            beatmeasure     =   struct.unpack("B", f.read(1))[0]
            smoothspeed     =   struct.unpack("B", f.read(1))[0]
            numrows         =   struct.unpack("I", f.read(4))[0]  
            freeze          =   (speed<0);
            bps             =   bpm / 60.0
            startbpm        =   bpm if startbpm == -1 else startbpm
            if not SUPRESS_PRINT:
                print   "\t    Block(%s)" % z
                print   "\t        StepTime:               %s" %steptime
                print   "\t        BPM:                    %s" %bpm
                print   "\t        Mystery Block:          %s" %mysteryblock
                print   "\t        Delay:                  %s" %delay
                print   "\t        Freeze:                 %s" %freeze
                print   "\t        Speed:                  %s" %speed
                print   "\t        Beat Split:             %s" %beatsplit
                print   "\t        Beat Measure:           %s" %beatmeasure
                print   "\t        Smooth Speed:           %s" %smoothspeed
                print   "\t        Number of Rows:         %s" %numrows

            if divisionconds > 0:
                #   Skip that conditions
                divd = f.read(4*10*2)

            if lightmap:
                if numrows > 0:
                    lmpd = f.read(4*numrows)
            else:
                speed   =   abs(speed)
                #    BPM Changes
                if lastbpm != bpm:
                    bpmchanges.append({"time": steptime/1000, "bpm" : bpm})
                    lastbpm = bpm            
                #    Delays
                if time == 0:
                    time = steptime 
                    beat = steptime * bps
                else:
                    beat += delay * bps
                    time += delay          

                #    ScrollFactor
                sf  =   mysteryblock * beatsplit
                lastsf = {"beat" : beat, "factor" : sf, "smooth" : smoothspeed>0, "smoothtime" : steptime}
                if lastsf != None:
                    lastsf["smoothtime"] = (steptime - lastsf["smoothtime"]) / 1000
                    sfchanges.append(lastsf)
                    if not SUPRESS_PRINT:
                        print "        Adding Scrollfactor Change - Factor: %s Smooth: %s Beat: %s SmoothTime: %s" %(lastsf["factor"],lastsf["smooth"],lastsf["beat"],lastsf["smoothtime"])
                
                lastsf = {"beat" : beat, "factor" : sf, "smooth" : smoothspeed>0, "smoothtime" : steptime}

                if lastss != None:
                    lastss["smoothtime"] = (steptime - lastss["smoothtime"]) / 1000
                    sschanges.append(lastss)
                    if not SUPRESS_PRINT:
                        print "        Adding ScrollSpeed Change - Factor: %s Smooth: %s Beat: %s SmoothTime: %s" %(lastss["factor"],lastss["smooth"],lastss["beat"],lastss["smoothtime"])
                
                lastss = {"beat" : beat, "factor" : speed, "smooth" : smoothspeed>0, "smoothtime" : steptime}

                if s == numsplits-1:
                    if not SUPRESS_PRINT:
                        print "        Adding last ss"
                    sschanges.append(lastss)

                #    Check for warps
                if round(steptime/1000) != round(time):
                    # Lets do an warp!
                    WarpBeat = ((steptime/1000) - time ) * bps + beat;
                    if not SUPRESS_PRINT:
                        print "        Time Warp from %s ms to %s ms from beat %s to beat %s" %(steptime/1000,time,WarpBeat,beat)
                    #warps.append({"beatFrom":beat,"beatTo":WarpBeat})
                    warps.append({"time":(steptime/1000),"beatTo":beat})
                      
                if numrows > 0:
                    rowd = struct.unpack("%sI"%numrows, f.read(4*numrows))
                    for r in range(numrows):
                        notes = []
                        if rowd[r] > 0:
                            f.seek(rowd[r])
                            for p in range(numcolumns):
                                data = struct.unpack("2B", f.read(2))
                                #{"type":NOTE_NX102SFC[stepdata[0]],"seed": stepdata[1],"skin":stepdata[2],"trigger":0}
                                notedata = ParseStepNX10([data[0], data[1] & 0xFC, data[1] & 0x03])
                                notedata["beat"] = beat
                                notes.append(notedata)
                            rows.append({"beat":beat, "notes":notes})
                        if bps != 0:
                            beat += 1.0 / beatsplit
                            time += 1.0 / (beatsplit * bps)
    f.close()
    data = GenSFC(chart["id"], chart["level"], chart["type"], startbpm, chart["title"], chart["author"], bpmchanges, warps, rushchanges, sfchanges, sschanges, tcchanges, effects, bgchanges, rows)
    return data