import sys, json, zipfile, audioop, hashlib, wave, io

sys.setrecursionlimit(4100)

def printWarning(message):
    print("WARNING: " + message)

def printError(message, gui):
    if gui:
        messagebox.showerror('Error', message, icon='warning')
    else:
        lines = message.split('\n')
        lines = ["       " + line for line in lines]
        lines[0] = "ERROR: " + lines[0][7:]
        print('\n'.join(lines))
        input('Press enter to exit... ')
    exit()

def rightPad(s, l, c):
    assert type(s) == str and type(c) == str
    return s + (l * c)

class BlockArgMapper:
    
    stageAttrs = {
        'backdrop #',
        'backdrop name',
        'volume'
    }

    spriteAttrs = {
        'x position',
        'y position',
        'direction',
        'costume #',
        'costume name',
        'size',
        'volume'
    }

    def __init__(self, obj):
        assert type(obj) == ProjectConverter
        self.converter = obj

    def mapArgs(self, opcode, block, blocks):
        assert not opcode.startswith('__')
        return getattr(self, opcode)(block, blocks)

    def varName(self, name):
        if type(name) == str:
            return ('_' if self.converter.compat else '') + name
        else:
            if self.converter.compat:
                return ['concatenate:with:', '_', name]
            else:
                return name

    # Motion

    def motion_movesteps(self, block, blocks):
        output = ['forward:']
        output.append(self.converter.inputVal('STEPS', block, blocks))
        return output

    def motion_turnright(self, block, blocks):
        output = ['turnRight:']
        output.append(self.converter.inputVal('DEGREES', block, blocks))
        return output

    def motion_turnleft(self, block, blocks):
        output = ['turnLeft:']
        output.append(self.converter.inputVal('DEGREES', block, blocks))
        return output

    def motion_pointindirection(self, block, blocks):
        output = ['heading:']
        output.append(self.converter.inputVal('DIRECTION', block, blocks))
        return output

    def motion_pointtowards(self, block, blocks):
        output = ['pointTowards:']
        output.append(self.converter.inputVal('TOWARDS', block, blocks))
        return output

    def motion_gotoxy(self, block, blocks):
        output = ['gotoX:y:']
        output.append(self.converter.inputVal('X', block, blocks))
        output.append(self.converter.inputVal('Y', block, blocks))
        return output

    def motion_goto(self, block, blocks):
        output = ['gotoSpriteOrMouse:']
        output.append(self.converter.inputVal('TO', block, blocks))
        return output

    def motion_glidesecstoxy(self, block, blocks):
        output = ['glideSecs:toX:y:elapsed:from:']
        output.append(self.converter.inputVal('SECS', block, blocks))
        output.append(self.converter.inputVal('X', block, blocks))
        output.append(self.converter.inputVal('Y', block, blocks))
        return output

    def motion_changexby(self, block, blocks):
        output = ['changeXposBy:']
        output.append(self.converter.inputVal('DX', block, blocks))
        return output

    def motion_setx(self, block, blocks):
        output = ['xpos:']
        output.append(self.converter.inputVal('X', block, blocks))
        return output

    def motion_changeyby(self, block, blocks):
        output = ['changeYposBy:']
        output.append(self.converter.inputVal('DY', block, blocks))
        return output

    def motion_sety(self, block, blocks):
        output = ['ypos:']
        output.append(self.converter.inputVal('Y', block, blocks))
        return output

    def motion_ifonedgebounce(self, block, blocks):
        return ['bounceOffEdge']

    def motion_setrotationstyle(self, block, blocks):
        output = ['setRotationStyle']
        output.append(self.converter.fieldVal('STYLE', block))
        return output

    def motion_xposition(self, block, blocks):
        return ['xpos']

    def motion_yposition(self, block, blocks):
        return ['ypos']

    def motion_direction(self, block, blocks):
        return ['heading']

    def motion_scroll_right(self, block, blocks):
        output = ['scrollRight']
        output.append(self.converter.inputVal('DISTANCE', block, blocks))
        return output

    def motion_scroll_up(self, block, blocks):
        output = ['scrollUp']
        output.append(self.converter.inputVal('DISTANCE', block, blocks))
        return output

    def motion_align_scene(self, block, blocks):
        output = ['scrollAlign']
        output.append(self.converter.fieldVal('ALIGNMENT', block))
        return output

    def motion_xscroll(self, block, blocks):
        return ['xScroll']

    def motion_yscroll(self, block, blocks):
        return ['yScroll']

    # Looks
    
    def looks_sayforsecs(self, block, blocks):
        output = ['say:duration:elapsed:from:']
        output.append(self.converter.inputVal('MESSAGE', block, blocks))
        output.append(self.converter.inputVal('SECS', block, blocks))
        return output

    def looks_say(self, block, blocks):
        output = ['say:']
        output.append(self.converter.inputVal('MESSAGE', block, blocks))
        return output

    def looks_thinkforsecs(self, block, blocks):
        output = ['think:duration:elapsed:from:']
        output.append(self.converter.inputVal('MESSAGE', block, blocks))
        output.append(self.converter.inputVal('SECS', block, blocks))
        return output

    def looks_think(self, block, blocks):
        output = ['think:']
        output.append(self.converter.inputVal('MESSAGE', block, blocks))
        return output

    def looks_show(self, block, blocks):
        return ['show']

    def looks_hide(self, block, blocks):
        return ['hide']

    def looks_hideallsprites(self, block, blocks):
        return ['hideAll']

    def looks_switchcostumeto(self, block, blocks):
        output = ['lookLike:']
        output.append(self.converter.inputVal('COSTUME', block, blocks))
        return output

    def looks_nextcostume(self, block, blocks):
        return ['nextCostume']

    def looks_switchbackdropto(self, block, blocks):
        output = ['startScene']
        output.append(self.converter.inputVal('BACKDROP', block, blocks))
        return output

    def looks_nextbackdrop(self, block, blocks):
        return ['nextScene']
    
    def looks_changeeffectby(self, block, blocks):
        output = ['changeGraphicEffect:by:']
        field = self.converter.fieldVal('EFFECT', block)
        if type(field) == str:
            field = str.lower(field)
        output.append(field)
        output.append(self.converter.inputVal('CHANGE', block, blocks))
        return output

    def looks_seteffectto(self, block, blocks):
        output = ['setGraphicEffect:to:']
        field = self.converter.fieldVal('EFFECT', block)
        if type(field) == str:
            field = str.lower(field)
        output.append(field)
        output.append(self.converter.inputVal('VALUE', block, blocks))
        return output

    def looks_cleargraphiceffects(self, block, blocks):
        return ['filterReset']

    def looks_changesizeby(self, block, blocks):
        output = ['changeSizeBy:']
        output.append(self.converter.inputVal('CHANGE', block, blocks))
        return output

    def looks_setsizeto(self, block, blocks):
        output = ['setSizeTo:']
        output.append(self.converter.inputVal('SIZE', block, blocks))
        return output

    def looks_changestretchby(self, block, blocks):
        output = ['changeStretchBy:']
        output.append(self.converter.inputVal('CHANGE', block, blocks))
        return output

    def looks_setstretchto(self, block, blocks):
        output = ['setStretchTo:']
        output.append(self.converter.inputVal('STRETCH', block, blocks))
        return output

    def looks_gotofrontback(self, block, blocks):
        field = self.converter.fieldVal('FRONT_BACK', block)
        if field == 'front':
            return ['comeToFront']
        else:
            return ['goBackByLayers:', 1.79e+308]

    def looks_goforwardbackwardlayers(self, block, blocks):
        layers = self.converter.inputVal('NUM', block, blocks)
        field = self.converter.fieldVal('FORWARD_BACKWARD', block)
        if field == 'forward':
            if type(layers) == str:
                try:
                    layers = float(layers)
                except:
                    pass
            if type(layers) == float or type(layers) == int:
                layers *= -1
            else:
                layers = ['*', -1, layers]
        return ['goBackByLayers:', layers]

    def looks_costumenumbername(self, block, blocks):
        field = self.converter.fieldVal('NUMBER_NAME', block)
        if field == 'number':
            return ['costumeIndex']
        elif field == 'name':
            if not self.converter.convertingMonitors:
                if self.converter.compat: # Can't use getAttribute:of: because it doesn't work for clones
                    self.converter.costumeName = True
                    return ['getLine:ofList:', ['costumeIndex'], self.converter.compatVarName('costume names')]
                else:
                    self.converter.generateWarning("Incompatible block 'costume [name v]'")
                    self.converter.compatWarning = True
                    return ['costumeName']

    def looks_backdropnumbername(self, block, blocks):
        field = self.converter.fieldVal('NUMBER_NAME', block)
        if field == 'number':
            return ['backgroundIndex']
        elif field == 'name':
            return ['sceneName']

    def looks_size(self, block, blocks):
        return ['scale']

    def looks_switchbackdroptoandwait(self, block, blocks):
        output = ['startSceneAndWait']
        output.append(self.converter.inputVal('BACKDROP', block, blocks))
        return output

    # Sound
    
    def sound_play(self, block, blocks):
        output = ['playSound:']
        output.append(self.converter.inputVal('SOUND_MENU', block, blocks))
        return output

    def sound_playuntildone(self, block, blocks):
        output = ['doPlaySoundAndWait']
        output.append(self.converter.inputVal('SOUND_MENU', block, blocks))
        return output

    def sound_stopallsounds(self, block, blocks):
        return ['stopAllSounds']

    def sound_changevolumeby(self, block, blocks):
        output = ['changeVolumeBy:']
        output.append(self.converter.inputVal('VOLUME', block, blocks))
        return output

    def sound_setvolumeto(self, block, blocks):
        output = ['setVolumeTo:']
        output.append(self.converter.inputVal('VOLUME', block, blocks))
        return output

    def sound_volume(self, block, blocks):
        return ['volume']

    # Music

    def music_playDrumForBeats(self, block, blocks):
        output = ['playDrum']
        output.append(self.converter.inputVal('DRUM', block, blocks))
        output.append(self.converter.inputVal('BEATS', block, blocks))
        return output

    def music_midiPlayDrumForBeats(self, block, blocks):
        output = ['drum:duration:elapsed:from:']
        output.append(self.converter.inputVal('DRUM', block, blocks))
        output.append(self.converter.inputVal('BEATS', block, blocks))
        return output

    def music_restForBeats(self, block, blocks):
        output = ['rest:elapsed:from:']
        output.append(self.converter.inputVal('BEATS', block, blocks))
        return output

    def music_playNoteForBeats(self, block, blocks):
        output = ['noteOn:duration:elapsed:from:']
        output.append(self.converter.inputVal('NOTE', block, blocks))
        output.append(self.converter.inputVal('BEATS', block, blocks))
        return output

    def music_setInstrument(self, block, blocks):
        output = ['instrument:']
        output.append(self.converter.inputVal('INSTRUMENT', block, blocks))
        return output

    def music_midiSetInstrument(self, block, blocks):
        output = ['midiInstrument:']
        output.append(self.converter.inputVal('INSTRUMENT', block, blocks))
        return output

    def music_changeTempo(self, block, blocks):
        output = ['changeTempoBy:']
        output.append(self.converter.inputVal('TEMPO', block, blocks))
        return output

    def music_setTempo(self, block, blocks):
        output = ['setTempoTo:']
        output.append(self.converter.inputVal('TEMPO', block, blocks))
        return output

    def music_getTempo(self, block, blocks):
        return ['tempo']

    # Pen

    def pen_clear(self, block, blocks):
        return ['clearPenTrails']

    def pen_stamp(self, block, blocks):
        return ['stampCostume']

    def pen_penDown(self, block, blocks):
        if self.converter.compat:
            self.converter.penUpDown = True
            return ['call', 'pen down']
        else:
            return ['putPenDown']

    def pen_penUp(self, block, blocks):
        if self.converter.compat:
            self.converter.penUpDown = True
            return ['call', 'pen up']
        else:
            return ['putPenUp']

    def pen_setPenColorToColor(self, block, blocks):
        if self.converter.compat:
            self.converter.penColor = True
            output = ['call', 'set pen color to %c']
        else:
            output = ['penColor:']
        output.append(self.converter.inputVal('COLOR', block, blocks))
        return output

    def pen_changePenHueBy(self, block, blocks):
        if self.converter.compat:
            self.converter.penColor = True
            output = ['call', 'change pen color by %n']
        else:
            output = ['changePenHueBy:']
        output.append(self.converter.inputVal('HUE', block, blocks))
        return output

    def pen_setPenHueToNumber(self, block, blocks):
        if self.converter.compat:
            self.converter.penColor = True
            output = ['call', 'set pen color to %n']
        else:
            output = ['setPenHueTo:']
        output.append(self.converter.inputVal('HUE', block, blocks))
        return output

    def pen_changePenShadeBy(self, block, blocks):
        if self.converter.compat:
            self.converter.penColor = True
            output = ['call', 'change pen shade by %n']
        else:
            output = ['changePenShadeBy:']
        output.append(self.converter.inputVal('SHADE', block, blocks))
        return output

    def pen_setPenShadeToNumber(self, block, blocks):
        if self.converter.compat:
            self.converter.penColor = True
            output = ['call', 'set pen shade to %n']
        else:
            output = ['setPenShadeTo:']
        output.append(self.converter.inputVal('SHADE', block, blocks))
        return output

    def pen_changePenColorParamBy(self, block, blocks):
        param = self.converter.inputVal('COLOR_PARAM', block, blocks)
        value = self.converter.inputVal('VALUE', block, blocks)
        if self.converter.compat:
            self.converter.penColor = True
            return ['call', 'change pen %s by %n', param, value]
        else:
            self.converter.compatWarning = True
            if param == 'color':
                if type(value) == str:
                    try:
                        value = float(value)
                    except:
                        pass
                if type(value) == float or type(value) == int:
                    value *= 2
                else:
                    value = ['*', 2, value]
                output = ['changePenHueBy:']
                output.append(value)
                return output
            elif param == 'brightness':
                if type(value) == str:
                    try:
                        value = float(value)
                    except:
                        pass
                if type(value) == float or type(value) == int:
                    value /= 2
                else:
                    value = ['/', value, 2]
                output = ['changePenShadeBy:']
                output.append(value)
                return output
            else:
                if type(param) == list:
                    paramStr = '[blocks...]'
                else:
                    paramStr = param
                self.converter.generateWarning("Incompatible block 'change pen [{} v] by ({})'".format(paramStr, value))
                return ['pen_changeColorParamBy', value, param]
    
    def pen_setPenColorParamTo(self, block, blocks):
        param = self.converter.inputVal('COLOR_PARAM', block, blocks)
        value = self.converter.inputVal('VALUE', block, blocks)
        if self.converter.compat:
            self.converter.penColor = True
            return ['call', 'set pen %s to %n', param, value]
        else:
            self.converter.compatWarning = True
            if param == 'color':
                if type(value) == str:
                    try:
                        value = float(value)
                    except:
                        pass
                if type(value) == float or type(value) == int:
                    value *= 2
                else:
                    value = ['*', 2, value]
                output = ['setPenHueTo:']
                output.append(value)
                return output
            elif param == 'brightness':
                if type(value) == str:
                    try:
                        value = float(value)
                    except:
                        pass
                if type(value) == float or type(value) == int:
                    value /= 2
                else:
                    value = ['/', value, 2]
                output = ['setPenShadeTo:']
                output.append(value)
                return output
            else:
                if type(param) == list:
                    paramStr = '[blocks...]'
                else:
                    paramStr = param
                self.converter.generateWarning("Incompatible block 'set pen [{} v] to ({})'".format(paramStr, value))
                return ['pen_setPenColorParamTo', value, param]
    
    def pen_changePenSizeBy(self, block, blocks):
        output = ['changePenSizeBy:']
        output.append(self.converter.inputVal('SIZE', block, blocks))
        return output

    def pen_setPenSizeTo(self, block, blocks):
        output = ['penSize:']
        output.append(self.converter.inputVal('SIZE', block, blocks))
        return output

    # Events
    
    def event_whenflagclicked(self, block, blocks):
        return ['whenGreenFlag']

    def event_whenkeypressed(self, block, blocks):
        output = ['whenKeyPressed']
        output.append(self.converter.fieldVal('KEY_OPTION', block))
        return output

    def event_whenthisspriteclicked(self, block, blocks):
        return ['whenClicked']

    def event_whenstageclicked(self, block, blocks):
        return ['whenClicked']

    def event_whenbackdropswitchesto(self, block, blocks):
        output = ['whenSceneStarts']
        output.append(self.converter.fieldVal('BACKDROP', block))
        return output

    def event_whengreaterthan(self, block, blocks):
        output = ['whenSensorGreaterThan']
        field = self.converter.fieldVal('WHENGREATERTHANMENU', block)
        if type(field) == str:
            field = str.lower(field)
        output.append(field)
        output.append(self.converter.inputVal('VALUE', block, blocks))
        return output

    def event_whenbroadcastreceived(self, block, blocks):
        output = ['whenIReceive']
        output.append(self.converter.fieldVal('BROADCAST_OPTION', block))
        return output

    def event_broadcast(self, block, blocks):
        output = ['broadcast:']
        output.append(self.converter.inputVal('BROADCAST_INPUT', block, blocks))
        return output

    def event_broadcastandwait(self, block, blocks):
        output = ['doBroadcastAndWait']
        output.append(self.converter.inputVal('BROADCAST_INPUT', block, blocks))
        return output

    # Control
    
    def control_wait(self, block, blocks):
        output = ['wait:elapsed:from:']
        output.append(self.converter.inputVal('DURATION', block, blocks))
        return output

    def control_repeat(self, block, blocks):
        output = ['doRepeat']
        output.append(self.converter.inputVal('TIMES', block, blocks))
        output.append(self.converter.substackVal('SUBSTACK', block, blocks))
        return output

    def control_forever(self, block, blocks):
        output = ['doForever']
        output.append(self.converter.substackVal('SUBSTACK', block, blocks))
        return output

    def control_if(self, block, blocks):
        output = ['doIf']
        output.append(self.converter.inputVal('CONDITION', block, blocks))
        output.append(self.converter.substackVal('SUBSTACK', block, blocks))
        return output

    def control_if_else(self, block, blocks):
        output = ['doIfElse']
        output.append(self.converter.inputVal('CONDITION', block, blocks))
        output.append(self.converter.substackVal('SUBSTACK', block, blocks))
        output.append(self.converter.substackVal('SUBSTACK2', block, blocks))
        return output

    def control_wait_until(self, block, blocks):
        output = ['doWaitUntil']
        output.append(self.converter.inputVal('CONDITION', block, blocks))
        return output

    def control_repeat_until(self, block, blocks):
        output = ['doUntil']
        output.append(self.converter.inputVal('CONDITION', block, blocks))
        output.append(self.converter.substackVal('SUBSTACK', block, blocks))
        return output

    def control_while(self, block, blocks):
        output = ['doWhile']
        output.append(self.converter.inputVal('CONDITION', block, blocks))
        output.append(self.converter.substackVal('SUBSTACK', block, blocks))
        return output

    def control_for_each(self, block, blocks):
        output = ['doForLoop']
        output.append(self.converter.fieldVal('VARIABLE', block))
        output.append(self.converter.inputVal('VALUE', block, blocks))
        output.append(self.converter.substackVal('SUBSTACK', block, blocks))
        return output

    def control_stop(self, block, blocks):
        output = ['stopScripts']
        output.append(self.converter.fieldVal('STOP_OPTION', block))
        return output

    def control_start_as_clone(self, block, blocks):
        return ['whenCloned']

    def control_create_clone_of(self, block, blocks):
        output = ['createCloneOf']
        output.append(self.converter.inputVal('CLONE_OPTION', block, blocks))
        return output

    def control_delete_this_clone(self, block, blocks):
        return ['deleteClone']

    def control_get_counter(self, block, blocks):
        return ['COUNT']

    def control_incr_counter(self, block, blocks):
        return ['INCR_COUNT']

    def control_clear_counter(self, block, blocks):
        return ['CLR_COUNT']

    def control_all_at_once(self, block, blocks):
        output = ['warpSpeed']
        output.append(self.converter.substackVal('SUBSTACK', block, blocks))
        return output

    # Video Sensing

    def videoSensing_videoOn(self, block, blocks):
        output = ['senseVideoMotion']
        output.append(self.converter.inputVal('ATTRIBUTE', block, blocks))
        output.append(self.converter.inputVal('SUBJECT', block, blocks))
        return output

    def videoSensing_whenMotionGreaterThan(self, block, blocks):
        output = ['whenSensorGreaterThan', 'video motion']
        output.append(self.converter.inputVal('REFERENCE', block, blocks))
        return output

    def videoSensing_videoToggle(self, block, blocks):
        output = ['setVideoState']
        output.append(self.converter.inputVal('VIDEO_STATE', block, blocks))
        return output

    def videoSensing_setVideoTransparency(self, block, blocks):
        output = ['setVideoTransparency']
        output.append(self.converter.inputVal('TRANSPARENCY', block, blocks))
        return output

    # Sensing

    def sensing_touchingobject(self, block, blocks):
        output = ['touching:']
        output.append(self.converter.inputVal('TOUCHINGOBJECTMENU', block, blocks))
        return output

    def sensing_touchingcolor(self, block, blocks):
        output = ['touchingColor:']
        output.append(self.converter.inputVal('COLOR', block, blocks))
        return output

    def sensing_coloristouchingcolor(self, block, blocks):
        output = ['color:sees:']
        output.append(self.converter.inputVal('COLOR', block, blocks))
        output.append(self.converter.inputVal('COLOR2', block, blocks))
        return output

    def sensing_distanceto(self, block, blocks):
        output = ['distanceTo:']
        output.append(self.converter.inputVal('DISTANCETOMENU', block, blocks))
        return output

    def sensing_askandwait(self, block, blocks):
        output = ['doAsk']
        output.append(self.converter.inputVal('QUESTION', block, blocks))
        return output

    def sensing_answer(self, block, blocks):
        return ['answer']

    def sensing_keypressed(self, block, blocks):
        output = ['keyPressed:']
        output.append(self.converter.inputVal('KEY_OPTION', block, blocks))
        return output

    def sensing_mousedown(self, block, blocks):
        return ['mousePressed']

    def sensing_mousex(self, block, blocks):
        return ['mouseX']

    def sensing_mousey(self, block, blocks):
        return ['mouseY']

    def sensing_setdragmode(self, block, blocks):
        assert self.converter.compat
        self.converter.dragMode = True
        return ['call', 'set drag mode %s', self.converter.fieldVal('DRAG_MODE', block)]

    def sensing_loudness(self, block, blocks):
        return ['soundLevel']

    def sensing_loud(self, block, blocks):
        return ['isLoud']

    def sensing_timer(self, block, blocks):
        if self.converter.compat:
            self.converter.timerCompat = True
            return ['*', 86400, ['-', ['timestamp'], ['readVariable', 'reset time']]]
        else:
            self.converter.compatWarning = True
            return ['timer']

    def sensing_resettimer(self, block, blocks):
        if self.converter.compat:
            self.converter.resetTimer = True
            self.converter.timerCompat = True
            return ['call', 'reset timer']
        else:
            return ['timerReset']

    def sensing_of(self, block, blocks):
        attr = self.converter.fieldVal('PROPERTY', block)
        obj = self.converter.inputVal('OBJECT', block, blocks)
        if obj == '_stage_':
            if (type(attr) == list) or (attr not in BlockArgMapper.stageAttrs):
                attr = self.varName(attr)
        elif (type(attr) == list) or (attr not in BlockArgMapper.spriteAttrs):
            attr = self.varName(attr)
        return ['getAttribute:of:', attr, obj]

    def sensing_current(self, block, blocks):
        output = ['timeAndDate']
        field = self.converter.fieldVal('CURRENTMENU', block)
        if type(field) == str:
            field = str.lower(field)
        output.append(field)
        return output

    def sensing_dayssince2000(self, block, blocks):
        return ['timestamp']

    def sensing_username(self, block, blocks):
        if not self.converter.convertingMonitors:
            return ['getUserName']

    def sensing_userid(self, block, blocks):
        return ['getUserId']

    # Operators

    def operator_add(self, block, blocks):
        output = ['+']
        output.append(self.converter.inputVal('NUM1', block, blocks))
        output.append(self.converter.inputVal('NUM2', block, blocks))
        return output

    def operator_subtract(self, block, blocks):
        output = ['-']
        output.append(self.converter.inputVal('NUM1', block, blocks))
        output.append(self.converter.inputVal('NUM2', block, blocks))
        return output

    def operator_multiply(self, block, blocks):
        output = ['*']
        output.append(self.converter.inputVal('NUM1', block, blocks))
        output.append(self.converter.inputVal('NUM2', block, blocks))
        return output

    def operator_divide(self, block, blocks):
        output = ['/']
        output.append(self.converter.inputVal('NUM1', block, blocks))
        output.append(self.converter.inputVal('NUM2', block, blocks))
        return output

    def operator_random(self, block, blocks):
        output = ['randomFrom:to:']
        output.append(self.converter.inputVal('FROM', block, blocks))
        output.append(self.converter.inputVal('TO', block, blocks))
        return output

    def operator_gt(self, block, blocks):
        output = ['>']
        output.append(self.converter.inputVal('OPERAND1', block, blocks))
        output.append(self.converter.inputVal('OPERAND2', block, blocks))
        return output

    def operator_lt(self, block, blocks):
        output = ['<']
        output.append(self.converter.inputVal('OPERAND1', block, blocks))
        output.append(self.converter.inputVal('OPERAND2', block, blocks))
        return output

    def operator_equals(self, block, blocks):
        output = ['=']
        output.append(self.converter.inputVal('OPERAND1', block, blocks))
        output.append(self.converter.inputVal('OPERAND2', block, blocks))
        return output

    def operator_and(self, block, blocks):
        output = ['&']
        output.append(self.converter.inputVal('OPERAND1', block, blocks))
        output.append(self.converter.inputVal('OPERAND2', block, blocks))
        return output

    def operator_or(self, block, blocks):
        output = ['|']
        output.append(self.converter.inputVal('OPERAND1', block, blocks))
        output.append(self.converter.inputVal('OPERAND2', block, blocks))
        return output

    def operator_not(self, block, blocks):
        output = ['not']
        output.append(self.converter.inputVal('OPERAND', block, blocks))
        return output

    def operator_join(self, block, blocks):
        if self.converter.unlimJoin:
            self.converter.joinStr = True
            stackReporter = ['call', 'join %s %s']
            stackReporter.append(self.converter.inputVal('STRING1', block, blocks))
            stackReporter.append(self.converter.inputVal('STRING2', block, blocks))
            self.converter.compatStackReporters[-1].append(stackReporter)
            return ['getLine:ofList:', len(self.converter.compatStackReporters[-1]), self.converter.compatVarName('results')]
        else:
            output = ['concatenate:with:']
            output.append(self.converter.inputVal('STRING1', block, blocks))
            output.append(self.converter.inputVal('STRING2', block, blocks))
            return output

    def operator_letter_of(self, block, blocks):
        output = ['letter:of:']
        output.append(self.converter.inputVal('LETTER', block, blocks))
        output.append(self.converter.inputVal('STRING', block, blocks))
        return output

    def operator_length(self, block, blocks):
        output = ['stringLength:']
        output.append(self.converter.inputVal('STRING', block, blocks))
        return output

    def operator_contains(self, block, blocks):
        assert self.converter.compat
        self.converter.strContains = True
        stackReporter = ['call', '%s contains %s ?']
        stackReporter.append(self.converter.inputVal('STRING1', block, blocks))
        stackReporter.append(self.converter.inputVal('STRING2', block, blocks))
        self.converter.compatStackReporters[-1].append(stackReporter)
        return ['getLine:ofList:', len(self.converter.compatStackReporters[-1]), self.converter.compatVarName('results')]

    def operator_mod(self, block, blocks):
        output = ['%']
        output.append(self.converter.inputVal('NUM1', block, blocks))
        output.append(self.converter.inputVal('NUM2', block, blocks))
        return output

    def operator_round(self, block, blocks):
        output = ['rounded']
        output.append(self.converter.inputVal('NUM', block, blocks))
        return output

    def operator_mathop(self, block, blocks):
        output = ['computeFunction:of:']
        output.append(self.converter.fieldVal('OPERATOR', block))
        output.append(self.converter.inputVal('NUM', block, blocks))
        return output

    # Data

    def data_variable(self, block, blocks):
        output = ['readVariable']
        output.append(self.converter.fieldVal('VARIABLE', block))
        return output

    def data_setvariableto(self, block, blocks):
        output = ['setVar:to:']
        output.append(self.converter.fieldVal('VARIABLE', block))
        output.append(self.converter.inputVal('VALUE', block, blocks))
        return output

    def data_changevariableby(self, block, blocks):
        output = ['changeVar:by:']
        output.append(self.converter.fieldVal('VARIABLE', block))
        output.append(self.converter.inputVal('VALUE', block, blocks))
        return output

    def data_showvariable(self, block, blocks):
        output = ['showVariable:']
        output.append(self.converter.fieldVal('VARIABLE', block))
        return output

    def data_hidevariable(self, block, blocks):
        output = ['hideVariable:']
        output.append(self.converter.fieldVal('VARIABLE', block))
        return output

    def data_listcontents(self, block, blocks):
        output = ['contentsOfList:']
        output.append(self.converter.fieldVal('LIST', block))
        return output

    def data_addtolist(self, block, blocks):
        if self.converter.limList:
            self.converter.addList = True
            output = ['call', 'add %s to %m.list']
        else:
            output = ['append:toList:']
        output.append(self.converter.inputVal('ITEM', block, blocks))
        output.append(self.converter.fieldVal('LIST', block))
        return output

    def data_deleteoflist(self, block, blocks):
        output = ['deleteLine:ofList:']
        output.append(self.converter.inputVal('INDEX', block, blocks))
        output.append(self.converter.fieldVal('LIST', block))
        return output

    def data_deletealloflist(self, block, blocks):
        output = ['deleteLine:ofList:', 'all']
        output.append(self.converter.fieldVal('LIST', block))
        return output

    def data_insertatlist(self, block, blocks):
        if self.converter.limList:
            self.converter.insertList = True
            output = ['call', 'insert %s at %n of %m.list']
        else:
            output = ['insert:at:ofList:']
        output.append(self.converter.inputVal('ITEM', block, blocks))
        output.append(self.converter.inputVal('INDEX', block, blocks))
        output.append(self.converter.fieldVal('LIST', block))
        return output

    def data_replaceitemoflist(self, block, blocks):
        output = ['setLine:ofList:to:']
        output.append(self.converter.inputVal('INDEX', block, blocks))
        output.append(self.converter.fieldVal('LIST', block))
        output.append(self.converter.inputVal('ITEM', block, blocks))
        return output

    def data_itemoflist(self, block, blocks):
        output = ['getLine:ofList:']
        output.append(self.converter.inputVal('INDEX', block, blocks))
        output.append(self.converter.fieldVal('LIST', block))
        return output

    def data_itemnumoflist(self, block, blocks):
        assert self.converter.compat
        self.converter.listSearch = True
        stackReporter = ['call', 'item # of %s in %m.list']
        stackReporter.append(self.converter.inputVal('ITEM', block, blocks))
        stackReporter.append(self.converter.fieldVal('LIST', block))
        self.converter.compatStackReporters[-1].append(stackReporter)
        return ['getLine:ofList:', len(self.converter.compatStackReporters[-1]), self.converter.compatVarName('results')]

    def data_lengthoflist(self, block, blocks):
        output = ['lineCountOfList:']
        output.append(self.converter.fieldVal('LIST', block))
        return output

    def data_listcontainsitem(self, block, blocks):
        output = ['list:contains:']
        output.append(self.converter.fieldVal('LIST', block))
        output.append(self.converter.inputVal('ITEM', block, blocks))
        return output

    def data_showlist(self, block, blocks):
        output = ['showList:']
        output.append(self.converter.fieldVal('LIST', block))
        return output

    def data_hidelist(self, block, blocks):
        output = ['hideList:']
        output.append(self.converter.fieldVal('LIST', block))
        return output

    # Procedures

    def procedures_definition(self, block, blocks):
        block = blocks[block['inputs']['custom_block'][1]]
        procData = block['mutation']
        output = ['procDef']
        output.append(self.varName(procData['proccode']))
        output.append(json.loads(procData['argumentnames']))
        output.append(json.loads(procData['argumentdefaults']))
        if len(output[-1]) != len(output[-2]):
            output[-1] = len(output[-2]) * ['']
        warp = procData['warp']
        output.append(warp == 'true' or (type(warp) == bool and warp))
        return output

    def procedures_call(self, block, blocks):
        output = ['call']
        output.append(self.varName(block['mutation']['proccode']))
        ids = json.loads(block['mutation']['argumentids'])
        for i in ids:
            output.append(self.converter.inputVal(i, block, blocks))
        return output

    def argument_reporter_string_number(self, block, blocks):
        output = ['getParam']
        output.append(self.converter.fieldVal('VALUE', block))
        output.append('r')
        return output

    def argument_reporter_boolean(self, block, blocks):
        output = ['getParam']
        output.append(self.converter.fieldVal('VALUE', block))
        output.append('b')
        return output

    # LEGO WeDo 2.0

    def wedo2_motorOnFor(self, block, blocks):
        output = ['LEGO WeDo 2.0\u001fmotorOnFor']
        output.append(self.converter.inputVal('MOTOR_ID', block, blocks))
        output.append(self.converter.inputVal('DURATION', block, blocks))
        return output

    def wedo2_motorOn(self, block, blocks):
        output = ['LEGO WeDo 2.0\u001fmotorOn']
        output.append(self.converter.inputVal('MOTOR_ID', block, blocks))
        return output

    def wedo2_motorOff(self, block, blocks):
        output = ['LEGO WeDo 2.0\u001fmotorOff']
        output.append(self.converter.inputVal('MOTOR_ID', block, blocks))
        return output

    def wedo2_startMotorPower(self, block, blocks):
        output = ['LEGO WeDo 2.0\u001fstartMotorPower']
        output.append(self.converter.inputVal('MOTOR_ID', block, blocks))
        output.append(self.converter.inputVal('POWER', block, blocks))
        return output

    def wedo2_setMotorDirection(self, block, blocks):
        output = ['LEGO WeDo 2.0\u001fsetMotorDirection']
        output.append(self.converter.inputVal('MOTOR_ID', block, blocks))
        output.append(self.converter.inputVal('MOTOR_DIRECTION', block, blocks))
        return output

    def wedo2_setLightHue(self, block, blocks):
        output = ['LEGO WeDo 2.0\u001fsetLED']
        output.append(self.converter.inputVal('HUE', block, blocks))
        return output

    def wedo2_playNoteFor(self, block, blocks):
        output = ['LEGO WeDo 2.0\u001fplayNote']
        output.append(self.converter.inputVal('NOTE', block, blocks))
        output.append(self.converter.inputVal('DURATION', block, blocks))
        return output

    def wedo2_whenDistance(self, block, blocks):
        output = ['LEGO WeDo 2.0\u001fwhenDistance']
        output.append(self.converter.inputVal('OP', block, blocks))
        output.append(self.converter.inputVal('REFERENCE', block, blocks))
        return output

    def wedo2_whenTilted(self, block, blocks):
        output = ['LEGO WeDo 2.0\u001fwhenTilted']
        output.append(self.converter.inputVal('TILT_DIRECTION_ANY', block, blocks))
        return output

    def wedo2_getDistance(self, block, blocks):
        return ['LEGO WeDo 2.0\u001fgetDistance']

    def wedo2_isTilted(self, block, blocks):
        output = ['LEGO WeDo 2.0\u001fisTilted']
        output.append(self.converter.inputVal('TILT_DIRECTION_ANY', block, blocks))
        return output

    def wedo2_getTiltAngle(self, block, blocks):
        output = ['LEGO WeDo 2.0\u001fgetTilt']
        output.append(self.converter.inputVal('TILT_DIRECTION', block, blocks))
        return output

class ProjectConverter:

    varModes = {
        'default': 1,
        'large': 2,
        'slider': 3
    }

    rotationStyles = {
        'all around': 'normal',
        'left-right': 'leftRight',
        'don\'t rotate': 'none'
    }

    monitorColors = {
        'motion': 4877524,
        'looks': 9065943,
        'sound': 12272323,
        'music': 12272323,
        'sensing': 2926050,
        'data': 15629590
    }

    # Used to change variable names in hacked reporters if in compatibility mode
    sb2BlocksVarFields = {
        'setVar:to:': 1,
        'changeVar:by:': 1,
        'showVariable:': 1,
        'hideVariable:': 1,
        'readVariable': 1,
        'contentsOfList:': 1,
        'append:toList:': 2,
        'deleteLine:ofList:': 2,
        'insert:at:ofList:': 3,
        'setLine:ofList:to:': 2,
        'getLine:ofList:': 2,
        'lineCountOfList:': 1,
        'list:contains:': 1,
        'showList:': 1,
        'hideList:': 1
    }

    compatWarnings = {
        'sensing_setdragmode',
        'operator_contains',
        'data_itemnumoflist'
    }

    @staticmethod
    def hexToDec(hexNum):
        try:
            return int(hexNum[1:], 16)
        except:
            return hexNum

    @staticmethod
    def specialNum(num):
        if num == '-Infinity':
            return float('-inf')
        if num == 'Infinity':
            return float('inf')
        if num == 'NaN':
            return float('nan')
        return num

    def __init__(self):
        self.argmapper = BlockArgMapper(self)
        self.blockID = 0
        self.comments = []
        self.blockComments = {}
        self.compatStackReporters = []
        self.compatWarning = False
        self.convertingMonitors = False

    def varName(self, name):
        if type(name) == str:
            return ('_' if self.compat else '') + name
        else:
            if self.compat:
                return ['concatenate:with:', '_', name]
            else:
                return name

    def compatVarName(self, name):
        return ('Stage: ' if self.targetIsStage else '') + name

    def generateWarning(self, message):
        self.warnings += 1
        printWarning(message)

    def setCommentBlockID(self, id):
        if id in self.blockComments:
            self.comments[self.blockComments[id]][5] = self.blockID

    def convertHackedReporter(self, reporter):
        if self.compat: # Add underscore to variable names if in compatibility mode
            block = reporter[0]
            if block in ProjectConverter.sb2BlocksVarFields:
                index = ProjectConverter.sb2BlocksVarFields[block]
                reporter[index] = self.varName(reporter[index])
            elif block == 'getAttribute:of:':
                if reporter[2] == '_stage_':
                    if reporter[1] not in BlockArgMapper.stageAttrs:
                        reporter[1] = self.varName(reporter[1])
                elif reporter[1] not in BlockArgMapper.spriteAttrs:
                    reporter[1] = self.varName(reporter[1])
            for value in reporter:
                if type(value) == list:
                    self.convertHackedReporter(value)

    def convertBlock(self, block, blocks):
        opcode = block['opcode']
        try:
            output = self.argmapper.mapArgs(opcode, block, blocks)
            output.append(tuple([block['UID']]))
            return output
        except:
            if len(block['inputs']) == 0 and len(block['fields']) == 1 and block['shadow'] and not block['topLevel']:  # Menu opcodes and shadows
                return self.fieldVal(list(block['fields'].items())[0][0], block)
            elif block['shadow'] and block['topLevel']:
                # Probably an invisible block
                return None
            else:
                self.generateWarning("Incompatible opcode '{}'".format(opcode))
                if opcode in ProjectConverter.compatWarnings:
                    self.compatWarning = True

                output = [opcode]
                for i in block['inputs']:
                    value = self.inputVal(i, block, blocks)
                    output.append(value)
                for f in block['fields']:
                    value = self.fieldVal(f, block)
                    output.append(value)
                output.append(tuple([block['UID']]))
                return output

    def inputVal(self, value, block, blocks):

        if not value in block['inputs']:
            return False

        value = block['inputs'][value]
        if value[1] == None:
            return None
        if value[0] == 1:
            if type(value[1]) == str:
                return self.convertBlock(blocks[value[1]], blocks)
            else:
                output = value[1][1]
        else:
            out = value[1]
            if type(out) == list:
                if out[0] == 12:
                    return ['readVariable', self.varName(out[1])]
                elif out[0] == 13:
                    return ['contentsOfList:', self.varName(out[1])]
                else:
                    try:
                        return out[1]
                    except:
                        return
            else:
                try:
                    return self.convertBlock(blocks[out], blocks)
                except:
                    return False

        outType = value[1][0]
        if outType in [4, 5, 8]:
            try:
                string = str(output)
                output = float(output)
                if output % 1 == 0 and not ('.' in string):
                    output = int(output)
            except ValueError:
                pass
        elif outType in [6, 7]:
            try:
                output = float(output)
                if output % 1 == 0:
                    output = int(output)
            except ValueError:
                pass
        elif outType == 9:
            output = ProjectConverter.hexToDec(output)
        
        return ProjectConverter.specialNum(output)

    def fieldVal(self, value, block):

        if not value in block['fields']:
            return None

        output = block['fields'][value][0]
        if type(output) == list:
            self.convertHackedReporter(output)
        if value in ['VARIABLE', 'LIST']:
            output = self.varName(output)

        return output

    def convertSubstack(self, key, blocks):
        self.compatStackReporters.append([])
        block = blocks[key]
        script = []
        end = False
        while not end:
            self.compatStackReporters[-1] = []
            output = self.convertBlock(block, blocks)
            sReporters = self.compatStackReporters[-1]
            if len(sReporters) > 0:
                script.append(['deleteLine:ofList:', 'all', self.compatVarName('results')])
                script.extend(self.compatStackReporters[-1])
                if output[0] == 'doUntil':
                    if type(output[2]) != list:
                        output[2] = []
                    output[2].append(['deleteLine:ofList:', 'all', self.compatVarName('results')])
                    output[2].extend(self.compatStackReporters[-1])
            script.append(output)
            if block['next'] == None:
                end = True
            else:
                block = blocks[block['next']]
        del self.compatStackReporters[-1]
        return script

    def substackVal(self, stack, block, blocks):

        if not stack in block['inputs']:
            return None

        stack = block['inputs'][stack]
        if len(stack) < 2 or stack[1] == None:
            return []
        
        return self.convertSubstack(stack[1], blocks)

    def addComment(self, c):
        comment = []
        if c['x'] == None:
            x = None
        else:
            x = round(c['x'] / 1.5, 6)
            if x % 1 == 0:
                x = int(x)
        comment.append(x)
        if c['y'] == None:
            y = None
        else:
            y = round(c['y'] / 1.8, 6)
            if y % 1 == 0:
                y = int(y)
        comment.append(y)
        comment.append(c['width'])
        comment.append(c['height'])
        comment.append(not c['minimized'])
        comment.append(-1)
        comment.append(c['text'])

        if c['blockId'] != None:
            self.blockComments[c['blockId']] = len(self.comments)

        self.comments.append(comment)

    def addSound(self, s):

        scount = s['sampleCount']
        srate = s['rate']

        if not s['assetId'] in self.soundAssets:

            md5 = s['assetId']
            self.soundAssets[s['assetId']] = [len(self.soundAssets)]

            if s['dataFormat'] == 'wav':
                f = self.zfsb3.open(s['md5ext'], 'r')
                wav = bytes(f.read())
                try:
                    wavData = wave.open(io.BytesIO(wav), 'rb')
                    sampData = wavData.readframes(wavData.getnframes())
                    width = wavData.getsampwidth()
                    channels = wavData.getnchannels()
                    srate = wavData.getframerate()
                    wavData.close()
                except:
                    # Original solution which works in most cases
                    sampData = wav[44:]
                    width = int.from_bytes(wav[34:36], byteorder='little') // 8
                    channels = int.from_bytes(wav[22:24], byteorder='little')
                    srate = int.from_bytes(wav[24:28], byteorder='little')

                modified = False
                error = width * channels == 0 or (len(sampData) - 44) % (width * channels) != 0
                
                if not error:
                    try:
                        if channels == 2: # Convert to mono
                            sampData = audioop.tomono(sampData, width, 1, 1)
                            modified = True
                        
                        if srate > 22050 and srate != 44100 and not error: # Downsample
                            sampData = audioop.ratecv(sampData, width, 1, srate, 22050, None)[0]
                            srate = 22050
                            modified = True
                    except:
                        error = True

                if modified:
                    size = len(sampData)
                    scount = size // width
                    try:
                        wavFile = io.BytesIO()
                        wavData = wave.open(wavFile, 'wb')
                        wavData.setsampwidth(width)
                        wavData.setnchannels(1)
                        wavData.setframerate(srate)
                        wavData.setnframes(scount)
                        wavData.writeframes(sampData)
                        wavData.close()
                        wavFile.seek(0)
                        wav = wavFile.read()
                    except:
                        # Original solution which works in most cases
                        wav = wav[0:22] + (1).to_bytes(2, byteorder='little') + srate.to_bytes(4, byteorder='little') + wav[28:40] + size.to_bytes(4, byteorder='little') + sampData
                    self.soundAssets[s['assetId']].append(False)
                    md5 = hashlib.md5(wav).hexdigest()
                elif error and not srate <= 22050 and not channels == 1:
                    srate = s['rate']
                    self.soundAssets[s['assetId']].append(True)
                else:
                    self.soundAssets[s['assetId']].append(False)

                self.zfsb2.writestr('{}.{}'.format(len(self.soundAssets) - 1, s['dataFormat']), wav)
                f.close()
            else:
                self.soundAssets[s['assetId']].append(False)
            
            self.soundAssets[s['assetId']].append(scount)
            self.soundAssets[s['assetId']].append(srate)
            self.soundAssets[s['assetId']].append(md5)

        if s['dataFormat'] != 'wav':
            self.generateWarning("Sound '{}' cannot be converted into WAV".format(s['name']))
        elif self.soundAssets[s['assetId']][1] == True:
            self.generateWarning("Sound '{}' cannot be converted to mono or downsampled".format(s['name']))

        fileData = self.soundAssets[s['assetId']]
        sound = {
            'soundName': s['name'],
            'soundID': fileData[0],
            'md5': fileData[4] + '.wav',
            'sampleCount': fileData[2],
            'rate': fileData[3],
            'format': ''
        }

        self.sounds.append(sound)

    def addCostume(self, c):

        if not c['assetId'] in self.costumeAssets:
            if 'md5ext' in c:
                md5ext = c['md5ext']
            else:
                md5ext = '{}.{}'.format(c['assetId'], c['dataFormat'])
            self.costumeAssets[c['assetId']] = [len(self.costumeAssets)]

            f = self.zfsb3.open(c['md5ext'], 'r')
            img = f.read()
            if c['dataFormat'] == 'svg':
                img = str(img, encoding='utf-8')

                # Remove incorrect attributes added by Scratch 3.0
                # The correct values are found in the style attribute

                img = img.replace('fill="undefined"', '')  # Remove undefined fill
                
                # Remove incorrect stroke-width
                if ';stroke-width:' in img: # Check if stroke-width is in style attribute (may incorrectly remove some stroke-width attributes)
                    left = 0 
                    while left != -1:
                        left = img.find('stroke-width="', left)
                        if left != -1:
                            right = img.find('"', left + 14) + 1
                            img = img[0:left] + '' + img[right:]
                            left = right

                # Reposition bitmap images to their correct position

                if 'image' in img:

                    left = 0
                    while left != -1:
                        left = img.find('<image ', left)
                        if left != -1:
                            right = img.find('xlink:href=', left)
                            image = img[left:right]

                            try:
                                xLeft = image.find('x="')
                                xRight = image.find('"', xLeft + 3)
                                trX = float(image[xLeft+3:xRight])
                                image = image[0:xLeft] + image[xRight+1:]

                                yLeft = image.find('y="')
                                yRight = image.find('"', yLeft + 3)
                                trY = float(image[yLeft+3:yRight])
                                image = image[0:yLeft] + image[yRight+1:]

                                transformLeft = image.find('transform="')
                                transformRight = image.find('"', transformLeft + 11)
                                image = image[0:transformRight] + 'translate({} {})'.format(trX, trY) + image[transformRight:]

                                img = img[0:left] + image + img[right:]
                            except:
                                # self.generateWarning("Costume '{}' may have incorrect bitmap image positioning".format(c['name']))
                                pass

                            left += 1

                # Replace tspan elements with text elements, which aren't supported by Scratch 2.0

                if 'tspan' in img:

                    newImg = ''
                    left = 0
                    while left != -1:
                        oldLeft = left
                        left = img.find('<text', left)
                        if left != -1:
                            newImg += img[oldLeft:left]
                            right = img.find('</text>', left) + 7

                            innerLeft = img.find('>', left) + 1
                            attrs = img[left:innerLeft - 1] + ' '
                            i = attrs.find('id="') # Remove id attribute
                            if i != -1:
                                j = attrs.find('"', i + 4)
                                attrs = attrs[0:i] + attrs[j+1:]
                            attrs = attrs.replace('font-family="Sans Serif"', 'font-family="Helvetica"')
                            attrs = attrs.replace('font-family="Serif"', 'font-family="Donegal"')
                            attrs = attrs.replace('font-family="Handwriting"', 'font-family="Gloria"')
                            attrs = attrs.replace('font-family="Curly"', 'font-family="Mystery"')
                            attrs = attrs.replace('xml:space="preserve"', '')

                            left = right

                            # Remove tspan elements
                            text = ''
                            lineCount = 0
                            content = img[innerLeft:right - 7]
                            if 'tspan' in content:
                                while innerLeft != -1:
                                    innerLeft = img.find('<tspan', innerLeft, right)
                                    if innerLeft != -1:
                                        lineCount += 1
                                        innerLeft += 6
                                        innerLeft = img.find('>', innerLeft, right) + 1
                                        innerRight = img.find('</tspan>', innerLeft, right)
                                        text += img[innerLeft:innerRight] + '\n'
                                        innerLeft = innerRight + 7
                                
                                text = text[0:-1]
                                text = text + '</text>'
                            else:
                                text += content + '</text>'

                            # Fix misplaced text
                            matLeft = attrs.find('matrix')
                            if matLeft != -1:
                                try:
                                    matRight = attrs.find('"', matLeft)
                                    matrix = attrs[matLeft:matRight].split(' ')
                                    x = matrix[-2]
                                    if x[-1] == ',':
                                        x = x[0:-1]
                                    scX = matrix[0][7:]
                                    if scX[-1] == ',':
                                        scX = scX[0:-1]
                                    matrix[-2] = str(float(x) - 2.5 * float(scX))
                                    scY = matrix[3]
                                    if scY[-1] == ',':
                                        scY = scY[0:-1]
                                    matrix[-1] = str(float(matrix[-1][0:-1]) + 2.5 * float(scY)) + ')'
                                    matrix = ' '.join(matrix)
                                    attrs = attrs[0:matLeft] + matrix + attrs[matRight:]
                                except:
                                    self.generateWarning("Costume '{}' may have incorrect text positioning".format(c['name']))
                            else:
                                trLeft = attrs.find('translate')
                                scLeft = attrs.find('scale')
                                if not (trLeft == -1 or scLeft == -1):
                                    try:
                                        scRight = attrs.find('"', scLeft)

                                        i = trLeft + 10
                                        trX = ''
                                        while attrs[i] not in ', ':
                                            trX += attrs[i]
                                            i += 1
                                        while attrs[i] in ', ':
                                            i += 1
                                        trY = ''
                                        while attrs[i] not in ' )':
                                            trY += attrs[i]
                                            i += 1
                                        trX = float(trX)
                                        trY = float(trY)

                                        i = scLeft + 6
                                        scX = ''
                                        while attrs[i] not in ', ':
                                            scX += attrs[i]
                                            i += 1
                                        while attrs[i] in ', ':
                                            i += 1
                                        scY = ''
                                        while attrs[i] not in ' )':
                                            scY += attrs[i]
                                            i += 1
                                        scX = float(scX)
                                        scY = float(scY)
                                        if lineCount > 1:
                                            trY -= 40 * scY
                                        else:
                                            trY -= 25 * scY
                                        matrix = 'matrix({} 0 0 {} {} {})'.format(scX, scY, trX, trY)
                                        attrs = attrs[0:trLeft] + matrix + attrs[scRight:]
                                    except:
                                        self.generateWarning("Costume '{}' may have incorrect text positioning".format(c['name']))

                            text = attrs + '>' + text
                            newImg += text
                            left = right
                        else:
                            newImg += img[oldLeft:]

                    img = newImg
                
                md5ext = hashlib.md5(img.encode('utf-8')).hexdigest() + '.svg'
            else:
                img = bytes(img)
            self.zfsb2.writestr('{}.{}'.format(len(self.costumeAssets) - 1, c['dataFormat']), img)
            f.close()

            self.costumeAssets[c['assetId']].append(md5ext)

        fileData = self.costumeAssets[c['assetId']]
        costume = {
            'costumeName': c['name'],
            'baseLayerID': fileData[0],
            'baseLayerMD5': fileData[1],
            'rotationCenterX': c['rotationCenterX'],
            'rotationCenterY': c['rotationCenterY']
        }

        if ('bitmapResolution' in c) and (fileData[1][-3:] != 'svg'):
            costume['bitmapResolution'] = c['bitmapResolution']
        else:
            costume['bitmapResolution'] = 1

        self.costumes.append(costume)

    def getCommentBlockIDs(self, script):
        if len(script) > 0:
            UID = script[-1]
            if type(UID) == tuple:
                self.setCommentBlockID(UID[0])
                del script[-1]
            if type(script[0]) == str:
                self.blockID += 1
            if script[0] == 'procDef':
                self.blockID += 1 + len(script[2])
            else:
                for block in script:
                    if type(block) == list:
                        self.getCommentBlockIDs(block)

    def convertTarget(self, target, index, maxLen):

        sprite = {}

        sprite['objName'] = target['name']
        self.targetName = sprite['objName']
        scripts = []
        variables = []
        lists = []
        self.sounds = []
        self.costumes = []
        self.comments = []

        isStage = target['isStage']
        self.targetIsStage = isStage

        self.costumeName = False
        self.dragMode = False
        if not isStage:
            self.dragMode = target['draggable']
        self.penUpDown = False
        self.joinStr = False
        self.strContains = False
        self.listSearch = False
        self.addList = False
        self.insertList = False
        self.penColor = False
        self.resetTimer = False

        for s in target['sounds']:
            self.addSound(s)

        for c in target['costumes']:
            self.addCostume(c)

        for key, v in target['variables'].items():
            variable = {
                'name': self.varName(v[0]),
                'value': ProjectConverter.specialNum(v[1]),
                'isPersistent': len(v) >= 3 and v[2]
            }
            variables.append(variable)

        for key, l in target['lists'].items():
            ls = {
                'listName': self.varName(l[0]),
                'contents': [ProjectConverter.specialNum(item) for item in l[1]],
                'isPersistent': False
            }
            lists.append(ls)

        self.blockComments = {}

        for key, c in target['comments'].items():
            self.addComment(c)

        blocks = target['blocks']

        for key, b in blocks.items():
            if type(b) == dict:
                b['UID'] = key

        for key, b in blocks.items():

            if type(b) == list:

                x = round(b[3] / 1.5, 6)
                if x % 1 == 0:
                    x = int(x)
                y = round(b[4] / 1.8, 6)
                if y % 1 == 0:
                    y = int(y)
                script = [x, y]

                if b[0] == 12:
                    script.append([['readVariable', self.varName(b[1])]])
                elif b[0] == 13:
                    script.append([['contentsOfList:', self.varName(b[1])]])
                else:
                    script = None

                if script != None:
                    scripts.append(script)
                    self.scriptCount += 1

            elif b['topLevel']:

                x = round(b['x'] / 1.5, 6)
                if x % 1 == 0:
                    x = int(x)
                y = round(b['y'] / 1.8, 6)
                if y % 1 == 0:
                    y = int(y)

                self.compatStackReporters = []
                substack = self.convertSubstack(key, blocks)

                if substack != [None]:
                    scripts.append([x, y, self.convertSubstack(key, blocks)])
                    self.scriptCount += 1

        # Find where the pen size is greater than 255 and add a screen fill

        if self.penFill:
            if self.compat and self.penUpDown:
                penDown = ['call', 'pen down']
                penUp = ['call', 'pen up']
            else:
                penDown = ['putPenDown']
                penUp = ['putPenUp']

            def scriptAddFill(script, down, up):
                if type(script) != list or len(script) < 1:
                    return
                if type(script[0]) == str:
                    if script[0] in ['penSize:', 'changePenSizeBy:']:
                        try:
                            if float(script[1]) >= 255.5:
                                return 'bigSize'
                            else:
                                return 'smallSize'
                        except:
                            return
                    else:
                        # Convert subscripts in repeats, ifs, etc.
                        if type(script[-1]) == tuple:
                            if len(script) > 2:
                                for block in script[1:-1]:
                                    scriptAddFill(block, down, up)
                        else:
                            if len(script) > 1:
                                for block in script[1:]:
                                    scriptAddFill(block, down, up)
                        return script
                else:
                    top = None
                    for i in range(len(script)):
                        value = scriptAddFill(script[i], down, up)
                        if value == 'bigSize':
                            self.bigSize = True
                        elif value == 'smallSize':
                            self.bigSize = False
                            top = None
                        elif type(value) != list or len(value) < 1:
                            continue
                        if value[0:len(down)] == down:
                            top = i
                        elif value[0:len(up)] == up and top != None and self.bigSize:
                            script[top:i+1] = [
                                ['penSize:', 200],
                                ['gotoX:y:', -350, -100],
                                down,
                                ['xpos:', 250],
                                up,
                                ['gotoX:y:', -350, 100],
                                down,
                                ['xpos:', 250],
                                up,
                                ['penSize:', 255]
                            ]
                            top = None
                        elif value[0] in ['call', 'broadcast:', 'doBroadcastAndWait', 'penColor:', 'changePenHueBy:', 'setPenHueTo:', 'changePenShadeBy:', 'setPenShadeTo:']:
                            top = None
            
            for script in scripts:    
                self.bigSize = False
                if len(script) >= 3:
                    scriptAddFill(script[2], penDown, penUp)
        
        self.blockID = 0
        for script in scripts:
            self.getCommentBlockIDs(script[2])

        # Add variables, lists, and custom blocks for compatibility mode

        if self.compat:

            if self.costumeName:
                costumeNames = []
                for c in target['costumes']:
                    costumeNames.append(c['name'])
                lists.append({
                    'listName': self.compatVarName('costume names'),
                    'contents': costumeNames,
                    'isPersistent': False,
                    'visible': False
                })

            if self.penUpDown or self.dragMode:

                pen = self.compatVarName('pen')
                variables.append({
                    'name': pen,
                    'value': 'up',
                    'isPersistent': False
                })

                scripts.append(
                    [0,
                        0,
                        [["procDef", "pen down", [], [], True], ["putPenDown"], ["setVar:to:", pen, "down"]]]
                )
                scripts.append(
                    [0,
                        0,
                        [["procDef", "pen up", [], [], True], ["putPenUp"], ["setVar:to:", pen, "up"]]]
                )

                self.scriptCount += 2

            if self.dragMode:

                drag = self.compatVarName('drag')
                variables.append({
                    'name': drag,
                    'value': 'draggable' if (not isStage and target['draggable']) else 'not draggable',
                    'isPersistent': False
                })

                scripts.append(
                    [0,
                        0,
                        [["whenClicked"],
                            ["doIf",
                                ["=", ["readVariable", drag], "draggable"],
                                [["call",
                                        "drag %n %n",
                                        ["-", ["xpos"], ["mouseX"]],
                                        ["-", ["ypos"], ["mouseY"]]]]]]]
                )

                scripts.append(
                    [0,
                        0,
                        [["procDef", "set drag mode %s", ["drag"], ["draggable"], True],
                            ["setVar:to:", drag, ["getParam", "drag", "r"]]]]
                )

                scripts.append(
                    [0,
                        0,
                        [["procDef", "drag %n %n", ["X", "Y"], [0, 0], False],
                            ['comeToFront'],
                            ["doIf", ["=", ["readVariable", pen], "down"], [["putPenUp"]]],
                            ["doUntil",
                                ["not", ["mousePressed"]],
                                [["gotoX:y:",
                                        ["+", ["mouseX"], ["getParam", "X", "r"]],
                                        ["+", ["mouseY"], ["getParam", "Y", "r"]]]]],
                            ["doIf", ["=", ["readVariable", pen], "down"], [["putPenDown"]]]]]
                )

                self.scriptCount += 3

            if self.joinStr or self.strContains or self.listSearch:
                returnVar = self.compatVarName('return')
                variables.append({
                    'name': returnVar,
                    'value': 0,
                    'isPersistent': False
                })
                results = self.compatVarName('results')
                lists.append({
                    'listName': results,
                    'contents': [],
                    'isPersistent': False,
                    'x': 0,
                    'y': 0,
                    'width': 100,
                    'height': 200,
                    'visible': False
                })

            if self.joinStr or self.strContains:
                i = self.compatVarName('i')
                variables.append({
                    'name': i,
                    'value': 0,
                    'isPersistent': False
                })

            if self.joinStr:
                joinList = self.compatVarName('join')
                lists.append({
                    'listName': joinList,
                    'contents': [],
                    'isPersistent': False,
                    'x': 0,
                    'y': 0,
                    'width': 100,
                    'height': 200,
                    'visible': False
                })
                scripts.append(
                    [0,
                        0,
                        [["procDef", "join %s %s", ["STRING1", "STRING2"], ["", ""], True],
                            ["doIfElse",
                                [">",
                                    ["+", ["stringLength:", ["getParam", "STRING1", "r"]], ["stringLength:", ["getParam", "STRING2", "r"]]],
                                    10240],
                                [["doIf",
                                        ["not", ["=", ["contentsOfList:", joinList], ["getParam", "STRING1", "r"]]],
                                        [["deleteLine:ofList:", "all", joinList],
                                            ["setVar:to:", "i", "0"],
                                            ["doRepeat",
                                                ["stringLength:", ["getParam", "STRING1", "r"]],
                                                [["changeVar:by:", "i", 1],
                                                    ["append:toList:", ["letter:of:", ["readVariable", "i"], ["getParam", "STRING1", "r"]], joinList]]]]],
                                    ["setVar:to:", "i", "0"],
                                    ["doRepeat",
                                        ["stringLength:", ["getParam", "STRING2", "r"]],
                                        [["changeVar:by:", "i", 1],
                                            ["append:toList:", ["letter:of:", ["readVariable", "i"], ["getParam", "STRING2", "r"]], joinList]]],
                                    ["append:toList:", ["contentsOfList:", joinList], "results"]],
                                [["append:toList:", ["concatenate:with:", ["getParam", "STRING1", "r"], ["getParam", "STRING2", "r"]], "results"]]]]]
                )
                self.scriptCount += 1
            
            if self.strContains:
                j = self.compatVarName('j')
                variables.append({
                    'name': j,
                    'value': 0,
                    'isPersistent': False
                })
                k = self.compatVarName('k')
                variables.append({
                    'name': k,
                    'value': 0,
                    'isPersistent': False
                })
                scripts.append(
                    [10,
                        10,
                        [["procDef", "%s contains %s ?", ["STRING1", "STRING2"], ["apple", "a"], True],
                            ["doIfElse",
                                ["=", ["stringLength:", ["getParam", "STRING2", "r"]], 0],
                                [["append:toList:", ["=", 0, 0], results]],
                                [["doIfElse",
                                        ["=", ["stringLength:", ["getParam", "STRING2", "r"]], 1],
                                        [["setVar:to:", i, 1],
                                            ["doRepeat",
                                                ["stringLength:", ["getParam", "STRING1", "r"]],
                                                [["doIf",
                                                        ["=",
                                                            ["letter:of:", ["readVariable", i], ["getParam", "STRING1", "r"]],
                                                            ["getParam", "STRING2", "r"]],
                                                        [["append:toList:", ["=", 0, 0], results], ["stopScripts", "this script"]]],
                                                    ["changeVar:by:", i, 1]]],
                                            ["append:toList:", ["=", 1, 0], results]],
                                        [["setVar:to:", k, ["-", ["stringLength:", ["getParam", "STRING2", "r"]], 1]],
                                            ["setVar:to:", i, 1],
                                            ["doRepeat",
                                                ["+",
                                                    ["-", ["stringLength:", ["getParam", "STRING1", "r"]], ["stringLength:", ["getParam", "STRING2", "r"]]],
                                                    1],
                                                [["setVar:to:", j, 0],
                                                    ["doIf",
                                                        ["=", ["readVariable", returnVar], "true"],
                                                        [["append:toList:", ["=", 0, 0], results], ["stopScripts", "this script"]]],
                                                    ["setVar:to:", returnVar, "true"],
                                                    ["doUntil",
                                                        [">", ["readVariable", j], ["readVariable", k]],
                                                        [["doIfElse",
                                                                ["=",
                                                                    ["letter:of:", ["+", ["readVariable", i], ["readVariable", j]], ["getParam", "STRING1", "r"]],
                                                                    ["letter:of:", ["+", ["readVariable", j], 1], ["getParam", "STRING2", "r"]]],
                                                                [["changeVar:by:", j, 1]],
                                                                [["setVar:to:", returnVar, "false"], ["setVar:to:", j, ["+", ["readVariable", k], 1]]]]]],
                                                    ["changeVar:by:", i, 1]]],
                                            ["append:toList:", ["=", 1, 0], results]]]]]]]
                )
                self.scriptCount += 1

            if self.listSearch:
                scripts.append(
                    [0,
                        0,
                        [["procDef", "item # of %s in %m.list", ["ITEM", "LIST"], ["thing", ""], True],
                            ["setVar:to:", returnVar, 0],
                            ["doIf",
                                ["list:contains:", ["getParam", "LIST", "r"], ["getParam", "ITEM", "r"]],
                                [["setVar:to:", returnVar, 0],
                                    ["doRepeat",
                                        ["lineCountOfList:", ["getParam", "LIST", "r"]],
                                        [["changeVar:by:", returnVar, 1],
                                            ["doIf",
                                                ["=",
                                                    ["getLine:ofList:", ["readVariable", returnVar], ["getParam", "LIST", "r"]],
                                                    ["getParam", "ITEM", "r"]],
                                                [["append:toList:", ["readVariable", returnVar], results], ["stopScripts", "this script"]]]]]]],
                                    ["append:toList:", 0, results]]]
                )
                self.scriptCount += 1

            if self.addList:
                scripts.append(
                    [0,
                        0,
                        [["procDef", "add %s to %m.list", ["ITEM", "LIST"], ["thing", ""], True],
                            ["doIf", ["<", ["lineCountOfList:", ["getParam", "LIST", "r"]], 200000], [["append:toList:", ["getParam", "ITEM", "r"], ["getParam", "LIST", "r"]]]]]]
                )
                self.scriptCount += 1
            
            if self.insertList:
                scripts.append(
                    [0,
                        0,
                        [["procDef", "insert %s at %n of %m.list", ["ITEM", "INDEX", "LIST"], ["thing", 1, ""], True],
                            ["insert:at:ofList:", ["getParam", "ITEM", "r"], ["getParam", "INDEX", "r"], ["getParam", "LIST", "r"]],
                            ["doIf", [">", ["lineCountOfList:", ["getParam", "LIST", "r"]], 200000], [["deleteLine:ofList:", "last", ["getParam", "LIST", "r"]]]]]]
                )
                self.scriptCount += 1

            if self.penColor:
                hue = self.compatVarName('hue')
                variables.append({
                    'name': hue,
                    'value': 200/3,
                    'isPersistent': False
                })
                sat = self.compatVarName('sat')
                variables.append({
                    'name': sat,
                    'value': 100,
                    'isPersistent': False
                })
                val = self.compatVarName('val')
                variables.append({
                    'name': val,
                    'value': 100,
                    'isPersistent': False
                })
                alpha = self.compatVarName('alpha')
                variables.append({
                    'name': alpha,
                    'value': 0,
                    'isPersistent': False
                })
                shade = self.compatVarName('shade')
                variables.append({
                    'name': shade,
                    'value': 50,
                    'isPersistent': False
                })
                minVar = self.compatVarName('min')
                variables.append({
                    'name': minVar,
                    'value': 200/3,
                    'isPersistent': False
                })
                maxVar = self.compatVarName('max')
                variables.append({
                    'name': maxVar,
                    'value': 100,
                    'isPersistent': False
                })
                diff = self.compatVarName('diff')
                variables.append({
                    'name': diff,
                    'value': 100,
                    'isPersistent': False
                })
                r = self.compatVarName('r')
                variables.append({
                    'name': r,
                    'value': 0,
                    'isPersistent': False
                })
                g = self.compatVarName('g')
                variables.append({
                    'name': g,
                    'value': 50,
                    'isPersistent': False
                })
                b = self.compatVarName('b')
                variables.append({
                    'name': b,
                    'value': 100,
                    'isPersistent': False
                })
                c = self.compatVarName('c')
                variables.append({
                    'name': c,
                    'value': 0,
                    'isPersistent': False
                })
                x = self.compatVarName('x')
                variables.append({
                    'name': x,
                    'value': 50,
                    'isPersistent': False
                })

                scripts.extend(
                    [[0,
                        0,
                        [["procDef", "set pen color to %c", ["COLOR"], [255], True],
                            ["doIfElse",
                                ["=", ["*", 1, ["getParam", "COLOR", "r"]], ["getParam", "COLOR", "r"]],
                                [["penColor:", ["getParam", "COLOR", "r"]],
                                    ["setVar:to:",
                                        alpha,
                                        ["%", ["computeFunction:of:", "floor", ["/", ["getParam", "COLOR", "r"], 16777216]], 256]],
                                    ["doIf",
                                        ["not", ["=", ["readVariable", alpha], 0]],
                                        [["setVar:to:",
                                                alpha,
                                                ["*", 100, ["-", 1, ["/", ["readVariable", alpha], 255]]]]]],
                                    ["call",
                                        "store RGB as HSV %n %n %n",
                                        ["/",
                                            ["%", ["computeFunction:of:", "floor", ["/", ["getParam", "COLOR", "r"], 65536]], 256],
                                            255],
                                        ["/",
                                            ["%", ["computeFunction:of:", "floor", ["/", ["getParam", "COLOR", "r"], 256]], 256],
                                            255],
                                        ["/", ["%", ["getParam", "COLOR", "r"], 256], 255]]],
                                [["doIfElse",
                                        ["=", ["letter:of:", 1, ["getParam", "COLOR", "r"]], "#"],
                                        [["doIfElse",
                                                ["=", ["stringLength:", ["getParam", "COLOR", "r"]], 7],
                                                [["call",
                                                        "set pen color to %c",
                                                        ["concatenate:with:",
                                                            "0x",
                                                            ["concatenate:with:",
                                                                ["concatenate:with:",
                                                                    ["letter:of:", 2, ["getParam", "COLOR", "r"]],
                                                                    ["letter:of:", 3, ["getParam", "COLOR", "r"]]],
                                                                ["concatenate:with:",
                                                                    ["concatenate:with:",
                                                                        ["letter:of:", 4, ["getParam", "COLOR", "r"]],
                                                                        ["letter:of:", 5, ["getParam", "COLOR", "r"]]],
                                                                    ["concatenate:with:",
                                                                        ["letter:of:", 6, ["getParam", "COLOR", "r"]],
                                                                        ["letter:of:", 7, ["getParam", "COLOR", "r"]]]]]]]],
                                                [["doIfElse",
                                                        ["=", ["stringLength:", ["getParam", "COLOR", "r"]], 4],
                                                        [["call",
                                                                "set pen color to %c",
                                                                ["concatenate:with:",
                                                                    "0x",
                                                                    ["concatenate:with:",
                                                                        ["concatenate:with:",
                                                                            ["letter:of:", 2, ["getParam", "COLOR", "r"]],
                                                                            ["letter:of:", 2, ["getParam", "COLOR", "r"]]],
                                                                        ["concatenate:with:",
                                                                            ["concatenate:with:",
                                                                                ["letter:of:", 3, ["getParam", "COLOR", "r"]],
                                                                                ["letter:of:", 3, ["getParam", "COLOR", "r"]]],
                                                                            ["concatenate:with:",
                                                                                ["letter:of:", 4, ["getParam", "COLOR", "r"]],
                                                                                ["letter:of:", 4, ["getParam", "COLOR", "r"]]]]]]]],
                                                        [["call", "set pen color to %c", -16777216]]]]]],
                                        [["call", "set pen color to %c", -16777216]]]]]]],
                    [0,
                        0,
                        [["procDef", "change pen %s by %n", ["COLOR_PARAM", "VALUE"], ["color", 10], True],
                            ["doIfElse",
                                ["=", ["getParam", "COLOR_PARAM", "r"], "color"],
                                [["call",
                                        "set pen %s to %n",
                                        ["getParam", "COLOR_PARAM", "r"],
                                        ["+", ["readVariable", hue], ["getParam", "VALUE", "r"]]]],
                                [["doIfElse",
                                        ["=", ["getParam", "COLOR_PARAM", "r"], "saturation"],
                                        [["call",
                                                "set pen %s to %n",
                                                ["getParam", "COLOR_PARAM", "r"],
                                                ["+", ["readVariable", sat], ["getParam", "VALUE", "r"]]]],
                                        [["doIfElse",
                                                ["=", ["getParam", "COLOR_PARAM", "r"], "brightness"],
                                                [["call",
                                                        "set pen %s to %n",
                                                        ["getParam", "COLOR_PARAM", "r"],
                                                        ["+", ["readVariable", val], ["getParam", "VALUE", "r"]]]],
                                                [["doIf",
                                                        ["=", ["getParam", "COLOR_PARAM", "r"], "transparency"],
                                                        [["call",
                                                                "set pen %s to %n",
                                                                ["getParam", "COLOR_PARAM", "r"],
                                                                ["+", ["readVariable", alpha], ["getParam", "VALUE", "r"]]]]]]]]]]]]],
                    [0,
                        0,
                        [["procDef", "change pen color by %n", ["HUE"], [10], True],
                            ["call", "change pen %s by %n", "color", ["/", ["getParam", "HUE", "r"], 2]]]],
                    [0,
                        0,
                        [["procDef", "set pen color to %n", ["HUE"], [0], True],
                            ["call", "set pen %s to %n", "color", ["/", ["getParam", "HUE", "r"], 2]]]],
                    [0,
                        0,
                        [["procDef", "change pen shade by %n", ["SHADE"], [10], True],
                            ["call", "set pen shade to %n", ["+", ["readVariable", shade], ["getParam", "SHADE", "r"]]]]],
                    [0,
                        0,
                        [["procDef", "set pen shade to %n", ["SHADE"], [50], True],
                            ["setVar:to:",
                                shade,
                                ["computeFunction:of:",
                                    "abs",
                                    ["-",
                                        ["getParam", "SHADE", "r"],
                                        ["*", 200, ["rounded", ["/", ["getParam", "SHADE", "r"], 200]]]]]],
                            ["call", "HSV to RGB %n %n %n", ["readVariable", hue], 100, 100],
                            ["doIfElse",
                                ["<", ["readVariable", shade], 50],
                                [["setVar:to:", x, ["/", ["+", ["readVariable", shade], 10], 60]],
                                    ["setVar:to:", r, ["rounded", ["*", ["readVariable", x], ["readVariable", r]]]],
                                    ["setVar:to:", g, ["rounded", ["*", ["readVariable", x], ["readVariable", g]]]],
                                    ["setVar:to:", b, ["rounded", ["*", ["readVariable", x], ["readVariable", b]]]]],
                                [["setVar:to:", x, ["/", ["-", ["readVariable", shade], 50], 60]],
                                    ["setVar:to:",
                                        r,
                                        ["rounded",
                                            ["+",
                                                ["*", ["-", 1, ["readVariable", x]], ["readVariable", r]],
                                                ["*", ["readVariable", x], 255]]]],
                                    ["setVar:to:",
                                        g,
                                        ["rounded",
                                            ["+",
                                                ["*", ["-", 1, ["readVariable", x]], ["readVariable", g]],
                                                ["*", ["readVariable", x], 255]]]],
                                    ["setVar:to:",
                                        b,
                                        ["rounded",
                                            ["+",
                                                ["*", ["-", 1, ["readVariable", x]], ["readVariable", b]],
                                                ["*", ["readVariable", x], 255]]]]]],
                            ["call",
                                "store RGB as HSV %n %n %n",
                                ["/", ["readVariable", r], 255],
                                ["/", ["readVariable", g], 255],
                                ["/", ["readVariable", b], 255]],
                            ["penColor:",
                                ["+",
                                    ["readVariable", b],
                                    ["*",
                                        256,
                                        ["+",
                                            ["readVariable", g],
                                            ["*",
                                                256,
                                                ["+",
                                                    ["readVariable", r],
                                                    ["*", 256, ["rounded", ["*", 2.55, ["-", 100, ["readVariable", alpha]]]]]]]]]]]]],
                    [0,
                        0,
                        [["procDef", "set pen %s to %n", ["COLOR_PARAM", "VALUE"], ["color", 50], True],
                            ["doIfElse",
                                ["=", ["getParam", "COLOR_PARAM", "r"], "color"],
                                [["setPenHueTo:", ["*", 2, ["getParam", "VALUE", "r"]]],
                                    ["setVar:to:", hue, ["%", ["getParam", "VALUE", "r"], 100]],
                                    ["stopScripts", "this script"]],
                                [["doIfElse",
                                        ["=", ["getParam", "COLOR_PARAM", "r"], "saturation"],
                                        [["doIfElse",
                                                ["<", ["getParam", "VALUE", "r"], 0],
                                                [["setVar:to:", sat, 0]],
                                                [["doIfElse",
                                                        [">", ["getParam", "VALUE", "r"], 100],
                                                        [["setVar:to:", sat, 100]],
                                                        [["setVar:to:", sat, ["getParam", "VALUE", "r"]]]]]]],
                                        [["doIfElse",
                                                ["=", ["getParam", "COLOR_PARAM", "r"], "brightness"],
                                                [["doIfElse",
                                                        ["<", ["getParam", "VALUE", "r"], 0],
                                                        [["setVar:to:", val, 0]],
                                                        [["doIfElse",
                                                                [">", ["getParam", "VALUE", "r"], 100],
                                                                [["setVar:to:", val, 100]],
                                                                [["setVar:to:", val, ["getParam", "VALUE", "r"]]]]]]],
                                                [["doIfElse",
                                                        ["=", ["getParam", "COLOR_PARAM", "r"], "transparency"],
                                                        [["doIfElse",
                                                                ["<", ["getParam", "VALUE", "r"], 0],
                                                                [["setVar:to:", alpha, 0]],
                                                                [["doIfElse",
                                                                        [">", ["getParam", "VALUE", "r"], 100],
                                                                        [["setVar:to:", alpha, 100]],
                                                                        [["setVar:to:", alpha, ["getParam", "VALUE", "r"]]]]]]],
                                                        [["stopScripts", "this script"]]]]]]]]],
                            ["call", "HSV to RGB %n %n %n", ["readVariable", hue], ["readVariable", sat], ["readVariable", val]],
                            ["penColor:",
                                ["+",
                                    ["readVariable", b],
                                    ["*",
                                        256,
                                        ["+",
                                            ["readVariable", g],
                                            ["*",
                                                256,
                                                ["+",
                                                    ["readVariable", r],
                                                    ["*", 256, ["rounded", ["*", 2.55, ["-", 100, ["readVariable", alpha]]]]]]]]]]]]],
                    [0,
                        0,
                        [["procDef", "store RGB as HSV %n %n %n", ["R", "G", "B"], [0, 0, 0], True],
                            ["doIfElse",
                                ["not",
                                    ["|",
                                        [">", ["getParam", "R", "r"], ["getParam", "G", "r"]],
                                        [">", ["getParam", "R", "r"], ["getParam", "B", "r"]]]],
                                [["setVar:to:", minVar, ["getParam", "R", "r"]]],
                                [["doIfElse",
                                        ["not",
                                            ["|",
                                                [">", ["getParam", "G", "r"], ["getParam", "R", "r"]],
                                                [">", ["getParam", "G", "r"], ["getParam", "B", "r"]]]],
                                        [["setVar:to:", minVar, ["getParam", "G", "r"]]],
                                        [["setVar:to:", minVar, ["getParam", "B", "r"]]]]]],
                            ["doIfElse",
                                ["not",
                                    ["|",
                                        ["<", ["getParam", "R", "r"], ["getParam", "G", "r"]],
                                        ["<", ["getParam", "R", "r"], ["getParam", "B", "r"]]]],
                                [["setVar:to:", maxVar, ["getParam", "R", "r"]]],
                                [["doIfElse",
                                        ["not",
                                            ["|",
                                                ["<", ["getParam", "G", "r"], ["getParam", "R", "r"]],
                                                ["<", ["getParam", "G", "r"], ["getParam", "B", "r"]]]],
                                        [["setVar:to:", maxVar, ["getParam", "G", "r"]]],
                                        [["setVar:to:", maxVar, ["getParam", "B", "r"]]]]]],
                            ["setVar:to:", diff, ["-", ["readVariable", maxVar], ["readVariable", minVar]]],
                            ["doIfElse",
                                ["=", ["readVariable", diff], 0],
                                [["setVar:to:", hue, 0], ["setVar:to:", sat, 0]],
                                [["doIfElse",
                                        ["=", ["readVariable", maxVar], ["getParam", "R", "r"]],
                                        [["setVar:to:",
                                                hue,
                                                ["/",
                                                    ["%",
                                                        ["/",
                                                            ["-", ["getParam", "G", "r"], ["getParam", "B", "r"]],
                                                            ["readVariable", diff]],
                                                        6],
                                                    0.06]]],
                                        [["doIfElse",
                                                ["=", ["readVariable", maxVar], ["getParam", "G", "r"]],
                                                [["setVar:to:",
                                                        hue,
                                                        ["/",
                                                            ["+",
                                                                ["/",
                                                                    ["-", ["getParam", "B", "r"], ["getParam", "R", "r"]],
                                                                    ["readVariable", diff]],
                                                                2],
                                                            0.06]]],
                                                [["setVar:to:",
                                                        hue,
                                                        ["/",
                                                            ["+",
                                                                ["/",
                                                                    ["-", ["getParam", "R", "r"], ["getParam", "G", "r"]],
                                                                    ["readVariable", diff]],
                                                                4],
                                                            0.06]]]]]],
                                    ["setVar:to:", sat, ["*", 100, ["/", ["readVariable", diff], ["readVariable", maxVar]]]]]],
                            ["setVar:to:", val, ["*", 100, ["readVariable", maxVar]]]]],
                    [0,
                        0,
                        [["procDef", "HSV to RGB %n %n %n", ["H", "S", "V"], [0, 0, 0], True],
                            ["setVar:to:",
                                c,
                                ["/", ["*", ["getParam", "V", "r"], ["getParam", "S", "r"]], 10000]],
                            ["setVar:to:",
                                x,
                                ["*",
                                    ["readVariable", c],
                                    ["-",
                                        1,
                                        ["computeFunction:of:",
                                            "abs",
                                            ["-", ["%", ["*", 0.06, ["getParam", "H", "r"]], 2], 1]]]]],
                            ["doIfElse",
                                ["<", ["getParam", "H", "r"], ["/", 100, 6]],
                                [["setVar:to:", r, ["readVariable", c]],
                                    ["setVar:to:", g, ["readVariable", x]],
                                    ["setVar:to:", b, 0]],
                                [["doIfElse",
                                        ["<", ["getParam", "H", "r"], ["/", 100, 3]],
                                        [["setVar:to:", r, ["readVariable", x]],
                                            ["setVar:to:", g, ["readVariable", c]],
                                            ["setVar:to:", b, 0]],
                                        [["doIfElse",
                                                ["<", ["getParam", "H", "r"], 50],
                                                [["setVar:to:", r, 0],
                                                    ["setVar:to:", g, ["readVariable", c]],
                                                    ["setVar:to:", b, ["readVariable", x]]],
                                                [["doIfElse",
                                                        ["<", ["getParam", "H", "r"], ["/", 200, 3]],
                                                        [["setVar:to:", r, 0],
                                                            ["setVar:to:", g, ["readVariable", x]],
                                                            ["setVar:to:", b, ["readVariable", c]]],
                                                        [["doIfElse",
                                                                ["<", ["getParam", "H", "r"], ["/", 250, 3]],
                                                                [["setVar:to:", r, ["readVariable", x]],
                                                                    ["setVar:to:", g, 0],
                                                                    ["setVar:to:", b, ["readVariable", c]]],
                                                                [["setVar:to:", r, ["readVariable", x]],
                                                                    ["setVar:to:", g, 0],
                                                                    ["setVar:to:", b, ["readVariable", c]]]]]]]]]]]],
                            ["setVar:to:",
                                x,
                                ["-", ["/", ["getParam", "V", "r"], 100], ["readVariable", c]]],
                            ["setVar:to:",
                                r,
                                ["rounded", ["*", 255, ["+", ["readVariable", r], ["readVariable", x]]]]],
                            ["setVar:to:",
                                g,
                                ["rounded", ["*", 255, ["+", ["readVariable", g], ["readVariable", x]]]]],
                            ["setVar:to:",
                                b,
                                ["rounded", ["*", 255, ["+", ["readVariable", b], ["readVariable", x]]]]]]]]
                )
                self.scriptCount += 9

            if self.resetTimer:
                scripts.append(
                    [0,
                        0,
                        [["procDef", "reset timer", [], [], True], ["setVar:to:", "reset time", ["timestamp"]], ["timerReset"]]]
                )
                self.scriptCount += 1

        sprite['scripts'] = scripts
        sprite['variables'] = variables
        sprite['lists'] = lists
        sprite['sounds'] = self.sounds
        sprite['costumes'] = self.costumes
        sprite['scriptComments'] = self.comments

        if isStage:

            sprite['currentCostumeIndex'] = target['currentCostume']
            sprite['tempoBPM'] = target['tempo']
            sprite['videoAlpha'] = (100 - target['videoTransparency']) / 100
            sprite['penLayerMD5'] = ''

            sprite['objName'] = 'Stage'
            sprite['info'] = {
                'userAgent': self.jsonData['meta']['agent'],
                'videoOn': target['videoState'] == 'on'
            }

        else:

            sprite['currentCostumeIndex'] = target['currentCostume']
            sprite['scratchX'] = target['x']
            sprite['scratchY'] = target['y']
            sprite['scale'] = target['size'] / 100
            sprite['direction'] = target['direction']
            sprite['rotationStyle'] = ProjectConverter.rotationStyles[target['rotationStyle']]
            sprite['isDraggable'] = target['draggable'] and not self.compat
            sprite['indexInLibrary'] = index
            sprite['visible'] = target['visible']
            sprite['spriteInfo'] = {}
            sprite['layerOrder'] = target['layerOrder']

        print("Converted {} ({}/{})".format(rightPad('\'' + sprite['objName'] + '\'', maxLen - len(sprite['objName']), ' '), index + 1, self.totalTargets))

        return (isStage, sprite)

    def updateList(self, l, ls):
        l['x'] = ls['x']
        l['y'] = ls['y']
        l['width'] = ls['width']
        l['height'] = ls['height']
        l['visible'] = ls['visible']

    # Update list data with monitor info

    def updateListData(self, output, sprites, stageLists, lists):

        for l in output['lists']:

            if l['listName'] in stageLists:
                ls = stageLists[l['listName']]
                self.updateList(l, ls)

        for s in sprites:
            spriteName = s['objName']

            if spriteName in lists:
                for l in s['lists']:

                    if l['listName'] in lists[spriteName]:
                        ls = lists[spriteName][l['listName']]
                        self.updateList(l, ls)

    def addMonitor(self, m):

        if m['opcode'] == 'data_listcontents':

            monitor = {
                'listName': self.varName(m['params']['LIST']),
                'contents': m['value'],
                'isPersistent': False,
                'x': m['x'],
                'y': m['y'],
                'width': 100 if m['width'] == 0 else m['width'],
                'height': 200 if m['height'] == 0 else m['height'],
                'visible': m['visible']
            }

            self.monitors.append(monitor)

            listData = {
                'x': m['x'],
                'y': m['y'],
                'width': monitor['width'],
                'height': monitor['height'],
                'visible': m['visible']
            }

            if m['spriteName'] == None:
                self.stageLists[monitor['listName']] = listData
            else:
                if m['spriteName'] not in self.lists:
                    self.lists[m['spriteName']] = {}
                self.lists[m['spriteName']][monitor['listName']] = listData

        else:

            try:
                block = {'opcode': m['opcode']}
                if 'params' in m:
                    block['fields'] = {}
                    for key, value in m['params'].items():
                        block['fields'][key] = [value]

                monitor = self.argmapper.mapArgs(m['opcode'], block, {})
                cmd = 'getVar:' if monitor[0] == 'readVariable' else monitor[0]
                if len(monitor) > 1:
                    param = monitor[1]
                else:
                    param = None

                assert cmd != None

                sMin = m['min'] if 'min' in m else m['sliderMin']
                sMax = m['max'] if 'max' in m else m['sliderMax']
                monitor = {
                    'target': 'Stage' if m['spriteName'] == None else m['spriteName'],
                    'cmd': cmd,
                    'param': param,
                    'color': ProjectConverter.monitorColors[m['opcode'].split('_')[0]],
                    'label': '',  # Scratch 2 will handle this
                    'mode': ProjectConverter.varModes[m['mode']],
                    'sliderMin': sMin,
                    'sliderMax': sMax,
                    'isDiscrete': sMin % 1 == 0 and sMax % 1 == 0 and not ('.' in str(sMin)) and not ('.' in str(sMax)),
                    'x': m['x'],
                    'y': m['y'],
                    'visible': m['visible']
                }
                self.monitors.append(monitor)

            except:
                self.generateWarning("Stage monitor '{}' will not be converted".format(m['opcode']))

    def convertProject(self, sb3path, sb2path, gui=False, replace=False, compatibility=False, unlimitedJoin=False, limitedLists=False, penFillScreen=False):

        self.compatWarning = False        
        self.compat = compatibility
        self.unlimJoin = unlimitedJoin
        self.limList = limitedLists
        self.penFill = penFillScreen

        self.warnings = 0

        if not sb3path[-3:] == 'sb3':
            printError("File '{}' is not an sb3 file".format(sb3path), gui)

        if not sb2path[-3:] == 'sb2':
            self.generateWarning("The converted project will be saved to '{}' instead of '{}'".format(sb2path + '.sb2', sb2path))
            sb2path += '.sb2'

        self.convertingMonitors = False

        try:
            self.zfsb3 = zipfile.ZipFile(sb3path, 'r')
        except:
            printError("File '{}' does not exist".format(sb3path), gui)

        print('')
        try:
            self.zfsb2 = zipfile.ZipFile(sb2path, 'x')
        except:
            replaceFile = False
            if replace:
                replaceFile = True
            else:
                print("File '{}' already exists".format(sb2path))
                replaceFile = input("Overwrite '{}'? (Y/N): ".format(sb2path))
                print('')
                replaceFile = replaceFile[0] == 'Y' or replaceFile[0] == 'y'
            if replaceFile:
                import os
                os.remove(sb2path)
                self.zfsb2 = zipfile.ZipFile(sb2path, 'x')
            else:
                exit()

        f = self.zfsb3.open('project.json', 'r')
        self.jsonData = json.loads(f.read())
        f.close()

        output = {}
        self.costumeAssets = {}
        self.soundAssets = {}

        sprites = []

        targetsDone = 0

        self.totalTargets = len(self.jsonData['targets'])
        self.scriptCount = 0

        # Convert Stage and sprites

        maxLen = 0
        for target in self.jsonData['targets']:
            name = 'Stage' if target['isStage'] else target['name']
            maxLen = max(maxLen, len(name))

        self.timerCompat = False

        for target in self.jsonData['targets']:
            sprite = self.convertTarget(target, targetsDone, maxLen)
            if sprite[0]:
                output = sprite[1]
            else:
                sprites.append(sprite[1])
            targetsDone += 1

        if self.timerCompat:
            output['variables'].append(
                {
                    'name': 'reset time',
                    'value': 0,
                    'isPersistent': False
                }
            )
            gfResetTimer = [0,
                0,
                [["whenGreenFlag"],
                    ["doIf",
                        [">",
                            ["-", ["*", 86400, ["-", ["timestamp"], ["readVariable", "reset time"]]], ["timer"]],
                            "0.1"],
                        [["setVar:to:", "reset time", ["-", ["timestamp"], ["/", ["timer"], 86400]]]]]]]
            output['scripts'].append(gfResetTimer)
            for sprite in sprites:
                sprite['scripts'].append(gfResetTimer)
            self.scriptCount += 1 + len(sprites)

        output['info']['scriptCount'] = self.scriptCount
        output['info']['spriteCount'] = self.totalTargets - 1

        self.monitors = []

        self.lists = {}
        self.stageLists = {}

        # Convert monitors

        self.convertingMonitors = True
        for m in self.jsonData['monitors']:
            self.addMonitor(m)

        self.updateListData(output, sprites, self.stageLists, self.lists)

        # Sort sprites into their layers

        sprites.sort(key = lambda sprite: sprite['layerOrder'])
        for sprite in sprites:
            del sprite['layerOrder']

        sprites.extend(self.monitors)
        self.convertingMonitors = False

        output['children'] = sprites

        # Add WeDo 2.0 extension if necessary

        if 'wedo2' in self.jsonData['extensions']:
            output['info']['savedExtensions'] = [{'extensionName': 'LEGO WeDo 2.0'}]

        output = json.dumps(output)

        self.zfsb2.writestr('project.json', output)

        self.zfsb3.close()
        self.zfsb2.close()

        return (self.warnings, self.compatWarning and not self.compat, sb2path)

def success(sb2path, warnings, gui):
    if gui:
        if warnings == 0:
            messagebox.showinfo("Success", "Completed with no warnings")
        elif warnings == 1:
            messagebox.showinfo("Success", "Completed with {} warning".format(warnings))
        else:
            messagebox.showinfo("Success", "Completed with {} warnings".format(warnings))
    else:
        print('')
        if warnings == 0:
            print("Saved to '{}' with no warnings".format(sb2path))
        elif warnings == 1:
            print("Saved to '{}' with {} warning".format(sb2path, warnings))
        else:
            print("Saved to '{}' with {} warnings".format(sb2path, warnings))

if __name__ == '__main__':

    if '-h' in sys.argv:

        print(
        '''
Arguments: sb3tosb2.py [unordered options] sb3path sb2path
List of Options:
-h: Show this list
-c: Enable Scratch 3.0 compatibility mode; Add workarounds for blocks that are exclusive to or work differently in 3.0
  The indented options will automatically enable compatibility mode:
  -j: Use an unlimited join workaround
  -l: Use custom blocks to automatically limit list length to 200,000
-p: Tries to insert blocks to fill the screen when the pen size is set to a value greater than 255''')
        exit()

    gui = False
    if len(sys.argv) < 3:
        gui = True
        import tkinter
        from tkinter import filedialog, messagebox

        root = tkinter.Tk()
        root.withdraw()
        sb3path = filedialog.askopenfilename(title="Open SB3 Project", filetypes=[("Scratch 3 Project", "*.sb3")])
        sb2path = filedialog.asksaveasfilename(title="Save as SB2 Project", filetypes=[("Scratch 2 Project", "*.sb2")])
    else:
        sb3path = sys.argv[-2]
        sb2path = sys.argv[-1]

    args = []
    if len(sys.argv) > 3:
        for arg in sys.argv[1:-2]:
            args.append(arg)

    args = ''.join(args)
    c = '-c' in args
    j = '-j' in args
    l = '-l' in args
    p = '-p' in args

    result = ProjectConverter().convertProject(sb3path, sb2path, gui=gui, replace=gui, compatibility=(c or j or l), unlimitedJoin=j, limitedLists=l, penFillScreen=p)
    warnings = result[0]
    sb2path = result[2]

    if result[1]:
        if gui:
            retry = messagebox.askquestion('Enable Compatibility Mode',
                "The converted project may not work properly unless compatibility mode is enabled.\n\nWould you like to re-convert the sb3 file with compatibility mode enabled?".format(sb3path), 
                icon='warning'
            )
            retry = (retry == 'yes')
        else:
            print('')
            printWarning("The converted project may not work properly unless compatibility mode is enabled.")
            retry = input("Would you like to re-convert '{}' with compatibility mode enabled? (Y/N): ".format(sb3path))
            retry = (retry[0] == 'Y' or retry[0] == 'y')

        if retry:
            result = ProjectConverter().convertProject(sb3path, sb2path, gui=gui, replace=True, compatibility=True, unlimitedJoin=j, limitedLists=l, penFillScreen=p)
            warnings = result[0]
            sb2path = result[2]

            success(sb2path, warnings, gui)
        else:
            success(sb2path, warnings, gui)
    else:
        success(sb2path, warnings, gui)