-- Multitrack v2 for FCEUX by FatRatKnight
-- Recent addition: Experimental save. Same format as in .fm2
-- Did not include support to not have it trigger on script exit. Sorry...
-- Additionally, there's no *loading* mechanism in this script. Eh...


--***************
local players= 2           -- You may tweak the number of players here.
--***************


-- Rewind options
local saveMax= 50          -- How many save states to keep? Set to 0 to disable
local SaveBuf= 12          -- How many frames between saves? Don't use 0, ever.
local rewind= "numpad0"    -- What key do you wish to hit for rewind?

--Keep in mind: Higher saveMax, more savestates, more memory needed.


--Control
local PlayerSwitch= "home" -- For selecting other players
local opt= "end"           -- For changing control options

local BackupReadSwitch=  "numpad-" -- Revert to backup copy
local BackupWriteSwitch= "numpad+" -- Copy input into backup

local key = {"right", "left", "down", "up",   "L",      "O",   "J", "K"}
local btn = {"right", "left", "down", "up", "start", "select", "B", "A"}

--Try to avoid changing btn. You may reorder btn's stuff if you don't
--like the default order, however. Line up with key for good reasons.
--key is the actual keyboard control over the buttons. Change that!


--Insertions and deletions
local Insert= "insert"     -- Inserts a frame at the frame you're at
local Delete= "delete"     -- Eliminates current frame


--Display
local BackupDisplaySwitch= "numpad." -- Care to see the backup?

local solid= "pageup"      -- Make the display less
local clear= "pagedown"    -- or more transparant.

local DispN= "numpad8"
local DispS= "numpad2"     -- For moving the
local DispE= "numpad6"     -- display around.
local DispW= "numpad4"

local MoreFutr= "numpad3"
local LessFutr= "numpad1"  -- These will determine
local MorePast= "numpad7"  -- how many frames you
local LessPast= "numpad9"  -- want to display.
local ResetFP=  "numpad5"


--Control options by keyboard
local OptUp= "numpad8"
local OptDn= "numpad2"     -- Controls for changing
local OptRt= "numpad6"     -- options by keyboard.
local OptLf= "numpad4"
local OptHit="numpad5"



--Various colors I'm using. If you wish to bother, go ahead.
local shade= 0x00000080
local white= "#FFFFFFFF"
local red=   "#FF2000FF"
local green= 0x00FF00FF
local blue=  0x0040FFFF
local orange="#FFC000FF"
local yellow="#FFFF00FF"
local cyan=  0x00FFFFFF
local purple="#8020FFFF"

--Default display stuff. Try to keep Past negative.
local DispX, DispY= 190, 70     --Position on screen to appear
local Past, Future= -12, 20     --How much input to see
local Opaque= 1                 --0=unseen   1=visible   Decimal in between works


local ListOfBtnClr= {  -- Colors for individual buttons
--NotExist Off      Ignored  On

  white,   red,     orange,  green,    -- No backup to display
  white,   red,     orange,  green,    -- Backup button is off
  blue,    blue,    purple,  cyan,     -- Backup button is on

  white,   red,     orange,  green,    -- Same stuff as above.
  white,   red,     orange,  green,    -- However, we're reading
  blue,    blue,    purple,  cyan,     -- from backup here.

  white,   red,     orange,  green,    -- Likewise, but instead
  white,   red,     orange,  green,    -- of read, we're writing
  blue,    blue,    purple,  cyan      -- to the backup. Joy...
}

local ListOfChangesClr= {  -- Colors for backup-main comparisons
--NoMain   NoMatch  Equal

  white,   green,   blue,              -- Backup just sits there...
  white,   orange,  green,             -- Reading from backup...
  white,   yellow,  purple             -- Writing to backup...
}



local BackupFileLimit= 9


--*****************************************************************************
--Please do not change the following, unless you plan to change the code:

local plmin , plmax= 1 , 1
local fc= movie.framecount()
local LastLoad= movie.framecount()
local saveCount= 0
local saveArray= {}
local rewinding= false

local BackupList= {}
local BackupOverride= {}
local DisplayBackup= true

local InputList= {}
local ThisInput= {}
local BufInput= {}
local BufLen= {}
local TrueSwitch, FalseSwitch= {}, {}
local ReadList= {}
local ScriptEdit= {}

for pl= 1, players do
    InputList[pl]= {}
    BackupList[pl]= {}
    BackupOverride[pl]= false
    ThisInput[pl]= {}
    BufInput[pl]= {}
    BufLen[pl]= 0

    TrueSwitch[pl]= {}
    FalseSwitch[pl]= {}
    ReadList[pl]= {}
    ScriptEdit[pl]= {}
    for i= 1, 8 do
        TrueSwitch[pl][i]= "inv"
        FalseSwitch[pl][i]= nil
        ReadList[pl][i]= true
        ScriptEdit[pl][i]= false
    end
end

--*****************************************************************************
-- Just painting instructions here.

print("Running Multitrack Script made by FatRatKnight.",
  "\r\nIts primary use is to preserve input after a loadstate.",
  "\r\nIt also gives precise control on changing this stored input.",
  "\r\nEdit the script if you need to change stuff. All options are near the top.",
  "\r\nThe script is currently set as follows:\r\n")

print("Players:",players)
if players > 1 then
    print("Player Switch key:",PlayerSwitch,
    "\r\nThere is an All Players view as well, beyond the last player.\r\n")
else
    print("With only one player, the PlayerSwitch button is disabled.\r\n")
end

print("Maximum rewind states:",saveMax)
local CalcSeconds= math.floor(saveMax*SaveBuf*100/60)/100
if saveMax > 0 then
    print("Frames between saves:",SaveBuf,
      "\r\nRewind key:",rewind,
      "\r\nThis can go backwards up to about",CalcSeconds,"seconds.",
      "\r\nRewind is a quick and easy way to back up just a few frames.",
      "\r\nJust hit",rewind,"and back you go!\r\n")
else
    print("Rewind function is disabled. Cheap on memory!\r\n")
end

print("Hold >",opt,"< to view control options.",
  "\r\nYou may use the mouse or keyboard to toggle options.",
  "\r\nFor keyboard, use",OptUp,OptDn,OptRt,OptLf,"to choose an option and",
  OptHit,"to toggle the option.",
  "\r\nFor mouse, it's as simple at point and click.",
  "\r\nIf viewing All Players, changing options will affect all players.\r\n")

print("For the frame display, you can drag it around with the mouse.",
  "\r\nTo move it by keyboard:",DispN,DispS,DispE,DispW,
  "\r\nTo change frames to display:",MoreFutr,LessFutr,MorePast,LessPast,
  "\r\nTo recenter display around current frame:",ResetFP,
  "\r\nLastly, hide it with >",clear,"< and show it with >",solid,"<\r\n")

print("Insert frame:",Insert,
  "\r\nDelete frame:",Delete,
  "\r\nThese keys allow you to shift frames around the current frame.",
      "Perhaps you found an improvement to a segment and want to delete frames.",
      "Or this small trick you wanted to add requires shifting a few frames forward.",
      "These buttons let you shift frames around without forcing you to",
      "modify each and every one of them individually.\r\n")


print("Script keys:",
  "\r\nJoy - Keyboard",
  "\r\n------------")
for i= 1, 8 do
    print(btn[i],"-",key[i])
end
print("By default, these are disabled. Enable them through the control options.",
  "\r\nThey are a one-tap joypad switch for precision control.",
  "\r\nIt will affect the currently displayed player, or all of them if",
      "it's displaying All Players.\r\n")

print("If you want to \"load\" an fm2 into this script, run the play file",
      "while running this script. As the movie on read-only plays out, the",
      "script will pick up its input. The script does not need to start from",
      "frame 1 -- Load a state near the end of a movie if you so wish!",
      "Once input is loaded, you may continue whatever you were doing.\r\n")

print("Remember, edit the script if you don't like the current options. All",
      "the options you should care about are near the top, clearly marked.",
      "Change the control keys through there, as well as number of players or rewind limits.",
  "\r\nThis script works with the joypad you actually set from FCEUX's config,",
      "and they can even cancel input stored in this script.",

"\r\n\r\nAnd have fun with this script. I hope your experiments with this script goes well.",
"\r\n\r\nLeeland Kirwan, the FatRatKnight.")

print("Players:",players,"  - -  ",CalcSeconds,"seconds of rewind.")



--*****************************************************************************
function FBoxOld(x1, y1, x2, y2, color)
--*****************************************************************************
-- Gets around FCEUX's problem of double-painting the corners.
-- The double-paint is visible with non-opaque drawing.
-- It acts like the old-style border-only box.
-- Slightly messes up when x2 or y2 are less than their counterparts.

    if     (x1 == x2) and (y1 == y2) then
        gui.pixel(x1,y1,color)

    elseif (x1 == x2) or  (y1 == y2) then
        gui.line(x1,y1,x2,y2,color)

    else --(x1 ~= x2) and (y1 ~= y2)
        gui.line(x1  ,y1  ,x2-1,y1  ,color) -- top
        gui.line(x2  ,y1  ,x2  ,y2-1,color) -- right
        gui.line(x1+1,y2  ,x2  ,y2  ,color) -- bottom
        gui.line(x1  ,y1+1,x1  ,y2  ,color) -- left
    end
end


--*****************************************************************************
function FakeBox(x1, y1, x2, y2, Fill, Border)
--*****************************************************************************
-- Gets around FCEUX's problem of double-painting the corners.
-- It acts like the new-style fill-and-border box.

if not Border then   Border= Fill   end

    gui.box(x1,y1,x2,y2,Fill,0)
    FBoxOld(x1,y1,x2,y2,Border)
end


--*****************************************************************************
local Draw= {}   --Draw[button]( Left , Top , color )
--*****************************************************************************

function Draw.right(x,y,color)       --    ##
    gui.line(x  ,y  ,x+1,y  ,color)  --      #
    gui.line(x  ,y+2,x+1,y+2,color)  --    ##
    gui.pixel(x+2,y+1,color)
end

function Draw.left(x,y,color)        --     ##
    gui.line(x+1,y  ,x+2,y  ,color)  --    #
    gui.line(x+1,y+2,x+2,y+2,color)  --     ##
    gui.pixel(x  ,y+1,color)
end

function Draw.up(x,y,color)          --     #
    gui.line(x  ,y+1,x  ,y+2,color)  --    # #
    gui.line(x+2,y+1,x+2,y+2,color)  --    # #
    gui.pixel(x+1,y  ,color)
end

function Draw.down(x,y,color)        --    # #
    gui.line(x  ,y  ,x  ,y+1,color)  --    # #
    gui.line(x+2,y  ,x+2,y+1,color)  --     #
    gui.pixel(x+1,y+2,color)
end

function Draw.start(x,y,color)       --     #
    gui.line(x+1,y  ,x+1,y+2,color)  --    ###
    gui.pixel(x  ,y+1,color)         --     #
    gui.pixel(x+2,y+1,color)
end

function Draw.select(x,y,color)      --    ###
    FBoxOld(x  ,y  ,x+2,y+2,color)   --    # #
end                                  --    ###

function Draw.A(x,y,color)           --    ###
    FBoxOld(x  ,y  ,x+2,y+1,color)   --    ###
    gui.pixel(x  ,y+2,color)         --    # #
    gui.pixel(x+2,y+2,color)
end

function Draw.B(x,y,color)           --    # #
    gui.line(x  ,y  ,x  ,y+2,color)  --    ##
    gui.line(x+1,y+1,x+2,y+2,color)  --    # #
    gui.pixel(x+2,y  ,color)
end



function Draw.D0(left, top, color)
    gui.line(left  ,top  ,left  ,top+4,color)
    gui.line(left+2,top  ,left+2,top+4,color)
    gui.pixel(left+1,top  ,color)
    gui.pixel(left+1,top+4,color)
end

function Draw.D1(left, top, color)
    gui.line(left  ,top+4,left+2,top+4,color)
    gui.line(left+1,top  ,left+1,top+3,color)
    gui.pixel(left  ,top+1,color)
end

function Draw.D2(left, top, color)
    gui.line(left  ,top  ,left+2,top  ,color)
    gui.line(left  ,top+3,left+2,top+1,color)
    gui.line(left  ,top+4,left+2,top+4,color)
    gui.pixel(left  ,top+2,color)
    gui.pixel(left+2,top+2,color)
end

function Draw.D3(left, top, color)
    gui.line(left  ,top  ,left+1,top  ,color)
    gui.line(left  ,top+2,left+1,top+2,color)
    gui.line(left  ,top+4,left+1,top+4,color)
    gui.line(left+2,top  ,left+2,top+4,color)
end

function Draw.D4(left, top, color)
    gui.line(left  ,top  ,left  ,top+2,color)
    gui.line(left+2,top  ,left+2,top+4,color)
    gui.pixel(left+1,top+2,color)
end

function Draw.D5(left, top, color)
    gui.line(left  ,top  ,left+2,top  ,color)
    gui.line(left  ,top+1,left+2,top+3,color)
    gui.line(left  ,top+4,left+2,top+4,color)
    gui.pixel(left  ,top+2,color)
    gui.pixel(left+2,top+2,color)
end

function Draw.D6(left, top, color)
    gui.line(left  ,top  ,left+2,top  ,color)
    gui.line(left  ,top+1,left  ,top+4,color)
    gui.line(left+2,top+2,left+2,top+4,color)
    gui.pixel(left+1,top+2,color)
    gui.pixel(left+1,top+4,color)
end

function Draw.D7(left, top, color)
    gui.line(left  ,top  ,left+1,top  ,color)
    gui.line(left+2,top  ,left+1,top+4,color)
end

function Draw.D8(left, top, color)
    gui.line(left  ,top  ,left  ,top+4,color)
    gui.line(left+2,top  ,left+2,top+4,color)
    gui.pixel(left+1,top  ,color)
    gui.pixel(left+1,top+2,color)
    gui.pixel(left+1,top+4,color)
end

function Draw.D9(left, top, color)
    gui.line(left  ,top  ,left  ,top+2,color)
    gui.line(left+2,top  ,left+2,top+3,color)
    gui.line(left  ,top+4,left+2,top+4,color)
    gui.pixel(left+1,top  ,color)
    gui.pixel(left+1,top+2,color)
end


Draw[0],Draw[1],Draw[2],Draw[3],Draw[4]=Draw.D0,Draw.D1,Draw.D2,Draw.D3,Draw.D4
Draw[5],Draw[6],Draw[7],Draw[8],Draw[9]=Draw.D5,Draw.D6,Draw.D7,Draw.D8,Draw.D9
--*****************************************************************************
function DrawNum(right, top, Number, color, bkgnd)
--*****************************************************************************
-- Paints the input number as right-aligned.
-- Returns the x position where it would paint another digit.
-- It only works with integers. Rounds fractions toward zero.

    local Digit= {}
    local Negative= false
    if Number < 0 then
        Number= -Number
        Negative= true
    end

    Number= math.floor(Number)
    if not color then color= "white" end
    if not bkgnd then bkgnd= "clear" end

    local i= 0
    if Number < 1 then
        Digit[1]= 0
        i= 1
    end

    while (Number >= 1) do
        i= i+1
        Digit[i]= Number % 10
        Number= math.floor(Number/10)
    end

    if Negative then  i= i+1  end
    local left= right - i*4
    FakeBox(left+1, top-1, right+1, top+5,bkgnd,bkgnd)

    i= 1
    while Draw[Digit[i]] do
        Draw[Digit[i]](right-2,top,color)
        right= right-4
        i=i+1
    end

    if Negative then
        gui.line(right, top+2,right-2,top+2,color)
        right= right-4
    end
    return right
end


--*****************************************************************************
function limits( value , low , high )  -- Expects numbers
--*****************************************************************************
-- Returns value, low, or high. high is returned if value exceeds high,
-- and low is returned if value is beneath low.

    return math.max(math.min(value,high),low)
end

--*****************************************************************************
function within( value , low , high )  -- Expects numbers
--*****************************************************************************
-- Returns true if value is between low and high. False otherwise.

    return ( value >= low ) and ( value <= high )
end


local keys, lastkeys= {}, {}
--*****************************************************************************
function press(button)  -- Expects a key
--*****************************************************************************
-- Returns true or false.
-- Generally, if keys is pressed, the next loop around will set lastkeys,
-- and thus prevent returning true more than once if held.

    return (keys[button] and not lastkeys[button])
end

local repeater= 0
--*****************************************************************************
function pressrepeater(button)  -- Expects a key
--*****************************************************************************
--DarkKobold & FatRatKnight
-- Returns true or false.
-- Acts much like press if you don't hold the key down. Once held long enough,
-- it will repeatedly return true.
-- Try not to call this function more than once per main loop, since I've only
-- set one repeater variable. Inside a for loop is real bad.

    if keys[button] then
       if not lastkeys[button] or repeater >= 3 then
           return true
       else
           repeater = repeater + 1;
       end

    elseif lastkeys[button] then   -- To allow more calls for other buttons
       repeater= 0
    end;
    return false
end



--*****************************************************************************
function JoyToNum(Joys)  -- Expects a table containing joypad buttons
--*****************************************************************************
-- Returns a number from 0 to 255, representing button presses.
-- These numbers are the primary storage for this version of this script.

    local joynum= 0

    for i= 1, 8 do
        if Joys[btn[i]] then
            joynum= bit.bor(joynum, bit.rshift(0x100,i))
        end
    end

    return joynum
end

--*****************************************************************************
function ReadJoynum(input, button)  -- Expects... Certain numbers!
--*****************************************************************************
-- Returns true or false. True if the indicated button is pressed
-- according to the input. False otherwise.
-- Helper function
    return ( bit.band(input , bit.rshift( 0x100,button )) ~= 0 )
end


--*****************************************************************************
function RewindThis()
--*****************************************************************************
--DarkKobold & FatRatKnight; Original concept by Antony Lavelle

    if saveCount <= 0 then
        return false    -- Failed to rewind
    else
        savestate.load(saveArray[saveCount]);
        saveCount = saveCount-1;
    end;
    return true         -- Yay, rewind!
end


--*****************************************************************************
function ShowOnePlayer(x,y,color,button,pl)
--*****************************************************************************
-- Displays an individual button.
-- Helper function for DisplayInput.

    x= x + 4*button - 3
    Draw[btn[button]](x,y,color)
end

--*****************************************************************************
function ShowManyPlayers(x,y,color,button,pl)
--*****************************************************************************
-- Displays an individual button. Uses 2x3 boxes instead of button graphics.
-- Helper function for DisplayInput.

    x= x + (2*players + 1)*(button - 1) + 2*pl - 1
    FBoxOld(x,y,x+1,y+2,color)
end


Future= limits(Future,Past,Past+53) --Idiot-proofing
--*****************************************************************************
function DisplayOptions(width)  -- Expects width calculated by DisplayInput
--*****************************************************************************
-- Returns true if Opaque is 0, as a signal to don't bother painting.
-- Separated from DisplayInput to make it clear which half is doing what.


-- Change opacity?
    if pressrepeater(solid) then Opaque= Opaque + 1/8 end
    if pressrepeater(clear) then Opaque= Opaque - 1/8 end
    Opaque= limits(Opaque,0,1)

    gui.opacity(Opaque)
    if Opaque == 0 then  return true  end
    -- Don't bother processing display if there's none to see.

-- How many frames to show?
    if pressrepeater(LessFutr) then
        Future= Future-1
        if Future < Past then Past= Future end
    end
    if pressrepeater(MoreFutr) then
        Future= Future+1
        if Future > Past+53 then Past= Future-53 end
    end
    if pressrepeater(MorePast) then
        Past= Past-1
        if Past < Future-53 then Future= Past+53 end
    end
    if pressrepeater(LessPast) then
        Past= Past+1
        if Past > Future then Future= Past end
    end

    if press(ResetFP) then Past= -12;  Future= 12   end


-- Move the display around?
    if pressrepeater(DispS) then DispY= DispY+1 end
    if pressrepeater(DispW) then DispX= DispX-1 end
    if pressrepeater(DispE) then DispX= DispX+1 end
    if pressrepeater(DispN) then DispY= DispY-1 end

    if keys["leftclick"] and lastkeys["leftclick"] then
        DispX= DispX + keys.xmouse - lastkeys.xmouse
        DispY= DispY + keys.ymouse - lastkeys.ymouse
    end

--Miscellaneous
    if press(BackupDisplaySwitch) then DisplayBackup= not DisplayBackup end
    DispX= limits(DispX,2,254-width)
    DispY= limits(DispY,3-4*Past,218-4*Future)


    return nil -- Signal to display the input
end


--*****************************************************************************
function DisplayInput()
--*****************************************************************************
-- Paints on the screen the current input stored within the script's list.

--Are we showing all players or just one?
    local HighlightButton= ShowOnePlayer
    local width= 32
    if plmin ~= plmax then
        HighlightButton= ShowManyPlayers
        width= 16*players + 8
    end

--For both setting options and asking if we should bother displaying ourselves
    if DisplayOptions(width) then  return  end

--Display frame offsets we're looking at.
    local RtDigit= DispX + width + 22
    if RtDigit > 254 then  RtDigit= DispX - 4  end
    local TpDigit= DispY + 4*Past - 2
    DrawNum(RtDigit,TpDigit,Past,white,shade)

    if Future > Past+1 then
        TpDigit= DispY + 4*Future
        DrawNum(RtDigit,TpDigit,Future,white,shade)
    end

--Show cute little box around current frame
    if Past <= 0 and Future >= 0 then
        local color= blue
        for pl= plmin, plmax do
            local ThisFrame= InputList[pl][fc-BufLen[pl]]
            if BufLen[pl] > 0 then  ThisFrame= BufInput[pl][1]  end
            if ThisFrame ~= JoyToNum(ThisInput[pl]) then  color= green  end
        end
        FBoxOld(DispX-1,DispY-2,DispX+width+1,DispY+4,color)
    end

--Shade the proper regions efficiently
    if Past < 0 then
        local Y1= DispY + 4*Past -3
        local Y2= DispY - 3
        if Future < -1 then  Y2= DispY + 4*Future +1  end
        FakeBox(DispX,Y1,DispX+width,Y2,shade,shade)
    end
    if Future > 0 then
        local Y1= DispY + 5
        local Y2= DispY + 4*Future+5
        if Past > 1 then  Y1= DispY + 4*Past +1  end
        FakeBox(DispX,Y1,DispX+width,Y2,shade,shade)
    end
    if  Past <= 0  and  Future >= 0  then
        FakeBox(DispX,DispY-1,DispX+width,DispY+3,shade,shade)
    end

--Finally, we get to show the actual buttons!
    for i= Past, Future do
        local Y= DispY + 4*i
        if     i < 0 then  Y=Y-2
        elseif i > 0 then  Y=Y+2 end

        local BackupColor= nil -- For displaying a line for backup
        for pl= plmin, plmax do
            local scanz
            if     i  < 0 then scanz= InputList[pl][fc+i]
            elseif i == 0 then scanz= JoyToNum(ThisInput[pl])
            elseif i  < BufLen[pl]  then
                               scanz= BufInput[pl][i+1]
            else               scanz= InputList[pl][fc+i-BufLen[pl]]
            end
            for button= 1, 8 do

-- Take a number, oh mighty color. There's too much of ya!
                local color
                if not scanz then
                    color= 1        --Does not exist
                elseif ReadJoynum(scanz,button) then
                    if (i <= 0) or (ReadList[pl][button]) then
                        color= 4    --Button on
                    else
                        color= 3    --Button on, will be ignored
                    end
                else
                    color= 2        --Button off
                end

                if DisplayBackup then
                    if BackupList[pl][fc+i] then
                        if ReadJoynum(BackupList[pl][fc+i],button) then
                            color= color+8
                        else
                            color= color+4
                        end
                    end
                    if     BackupOverride[pl] == "read"  then
                        color= color+12
                    elseif BackupOverride[pl] == "write" then
                        color= color+24
                    end
                end

                HighlightButton(DispX,Y,ListOfBtnClr[color],button,pl)

            end -- button loop

-- Draw cute lines if we wish to see the backup
            if DisplayBackup and BackupList[pl][fc+i] then
                if not BackUpColor then
                    if not scanz then
                        BackupColor= 1
                    elseif scanz == BackupList[pl][fc+i] then
                        BackupColor= 3
                    else
                        BackupColor= 2
                    end
                    if     BackupOverride[pl] == "read"  then
                        BackupColor= BackupColor+3
                    elseif BackupOverride[pl] == "write" then
                        BackupColor= BackupColor+6
                    end

                else -- Eh, we already have a BackupColor?
                    local CheckColor= (BackupColor-1)%3
                    if not scanz then
                        BackupColor= BackupColor - CheckColor
                    elseif (scanz ~= BackupList[pl][fc+i]) and (CheckColor == 2) then
                        BackupColor= BackupColor - 1
                    end
                end
            end

        end -- player loop

        if BackupColor then
            gui.line(DispX-2,Y,DispX-2,Y+2,ListOfChangesClr[BackupColor])
        end
    end -- loop from Past to Future
end --function


--*****************************************************************************
function SetInput()
--*****************************************************************************
-- Prepares ThisInput for joypad use.

    for pl= 1, players do
        local temp
        if BackupOverride[pl] == "read" then
            temp= BackupList[pl][fc]
        else
            if BufLen[pl] > 0 then
                temp= BufInput[pl][1]
            else
                temp= InputList[pl][fc-BufLen[pl]]
            end
        end

        for i= 1, 8 do
            if temp and ReadJoynum(temp,i) and ReadList[pl][i] then
                ThisInput[pl][btn[i]]= TrueSwitch[pl][i]
            else
                ThisInput[pl][btn[i]]= FalseSwitch[pl][i]
            end
        end
    end
end


--*****************************************************************************
function ApplyInput()
--*****************************************************************************
-- Shoves ThisInput into the actual emulator joypads.

    if movie.mode() ~= "playback" then
        for pl= 1, players do
            joypad.set(pl, ThisInput[pl])
        end
    end
end


--*****************************************************************************
function InputSnap()
--*****************************************************************************
-- Will shove the input list over.
-- Might end up freezing for a moment if there's a few thousand frames to shift

    for pl= 1, players do

       if BufLen[pl] < 0 then  -- Squish!
           local pointer= fc
           while InputList[pl][pointer] do
               InputList[pl][pointer]= InputList[pl][pointer-BufLen[pl]]
               pointer= pointer+1
           end
       end


       if BufLen[pl] > 0 then -- Foom!
           local pointer= fc
           while InputList[pl][pointer] do
               pointer= pointer+1
           end
           -- pointer is now looking at a null frame.
           -- Assume later frames are also null.

           while pointer > fc do
               pointer= pointer-1
               InputList[pl][pointer+BufLen[pl]]= InputList[pl][pointer]
           end
           -- pointer should now match fc.
           -- Everything at fc and beyond should be moved over by BufLen.

           for i=0, BufLen[pl]-1 do
               InputList[pl][fc +i]= table.remove(BufInput[pl],1)
           end
       end

       BufLen[pl]= 0 -- If it ain't zero before, we want it zero now!

    end
end




local XStart, XDiff= 30, 25
local YStart, YDiff= 12, 15
local Status= { {},{},{},{} }
local TargA= {FalseSwitch,TrueSwitch,ReadList,ScriptEdit}
local TrueA= {   nil     ,   "inv"  ,  true  ,  true    }
local FalsA= {   false   ,   true   ,  false ,  false   }
local BtnX, BtnY= 0, 0

local BasicText= {
"Activate: Lets the joypad activate input.",
"Remove: Allows the joypad to remove input.",
"List: Should the script remember button presses?",
"Keys: Allow the script-specific keys to work?"}

local AdvText= {
{"With both joypad options on, you have full control.",
 "Held buttons will toggle the script's stored input."}, -- 1

{"With this on-off set-up, auto-hold will not",
 "accidentally cancel input, such as from load state."}, -- 2

{"With this off-on set, you can use auto-hold to",
 "elegantly strip off unwanted input."},                 -- 3

{"With both joypad options off, you ensure you can't",
 "accidentally change what's stored in the script."},    -- 4

{"List on: Script will apply input stored within.",
 "This is the whole point of the script, ya know."},     -- 5

{"List off: Stored input is ignored. A good idea if",
 "you don't want the script interfering."},              -- 6

{"Keys on: Script keys will toggle the button press",
 "for the current frame. Meant for precise edits."},     -- 7

{"Keys off: Script-specific keys are no longer a",
 "factor. Long-term control is meant for joypad!"},      -- 8

{"Apparently, you've selected 'All players'",
 "and they have different options set."},                -- 9

{"This is the 'All' button. Selecting this will set",
 "all 'Yes' or all 'No' for this particular option."}    --10
}

--*****************************************************************************
function ControlOptions()
--*****************************************************************************
-- Lets the user make adjustments on the various controls of this script.
-- The interface, apparently, is most of the work!


-- Button selection
    if (keys.xmouse ~= lastkeys.xmouse) or (keys.ymouse ~= lastkeys.ymouse) then
        BtnX= math.floor((keys.xmouse- XStart+4)/XDiff)
        BtnY= math.floor((keys.ymouse- YStart-6)/YDiff)
    else
        if press(OptUp) then
            BtnX= limits(BtnX  ,1,4)
            BtnY= limits(BtnY-1,1,9)
        end
        if press(OptDn) then
            BtnX= limits(BtnX  ,1,4)
            BtnY= limits(BtnY+1,1,9)
        end
        if press(OptRt) then
            BtnX= limits(BtnX+1,1,4)
            BtnY= limits(BtnY  ,1,9)
        end
        if press(OptLf) then
            BtnX= limits(BtnX-1,1,4)
            BtnY= limits(BtnY  ,1,9)
        end
    end


--Did you punch something?
    if press("leftclick") or press(OptHit) then
        if within( BtnY , 1 , 9 ) then
            if within( BtnX , 1 , 4 ) then
                local LoopS, LoopF= 1, 8
                if BtnY ~= 9 then
                    LoopS, LoopF= BtnY, BtnY
                end

                local Target, TstT, TstF= TargA[BtnX], TrueA[BtnX], FalsA[BtnX]

                for pl= plmin, plmax do
                    for Loop= LoopS, LoopF do
                        if Target[plmax][LoopF] == TstT then
                            Target[pl][Loop]= TstF
                        else
                            Target[pl][Loop]= TstT
                        end
                    end
                end

            end
        end
    end


--Figure out the status
    for i= 1, 4 do
        for j= 1, 8 do
            Status[i][j]= TargA[i][plmin][j]
            local pl= plmin+1
            while (pl <= plmax) do
                if Status[i][j] ~= TargA[i][pl][j] then
                    Status[i][j]= "???"
                    break
                end
                pl= pl+1
            end
            if     Status[i][j] == TrueA[i] then  Status[i][j]= "Yes"
            elseif Status[i][j] == FalsA[i] then  Status[i][j]= "No" end
        end
    end

--=============================================================================
--Begin drawing. Finally...


--Shade the whole freaking screen
    FakeBox(0,0,255,223,shade,shade)

-- Silly little graphics
-- Perhaps I shouldn't always assume that button right is a right arrow...
    Draw.B(     XStart + XDiff     ,YStart,red)
    Draw.right( XStart + XDiff  + 5,YStart,white)
    Draw.B(     XStart + XDiff  +10,YStart,green)

    Draw.B(     XStart + XDiff*2   ,YStart,green)
    Draw.right( XStart + XDiff*2+ 5,YStart,white)
    Draw.B(     XStart + XDiff*2+10,YStart,red)

    Draw[btn[1]](XStart + XDiff*3   ,YStart  ,green)
    Draw[btn[1]](XStart + XDiff*3   ,YStart+4,green)
    Draw[btn[2]](XStart + XDiff*3+ 4,YStart  ,green)
    Draw[btn[2]](XStart + XDiff*3+ 4,YStart+4,red)
    Draw[btn[3]](XStart + XDiff*3+ 8,YStart  ,red)
    Draw[btn[3]](XStart + XDiff*3+ 8,YStart+4,green)
    Draw[btn[4]](XStart + XDiff*3+12,YStart  ,red)
    Draw[btn[4]](XStart + XDiff*3+12,YStart+4,red)

    gui.box(XStart+XDiff*4,YStart,XStart+XDiff*4+14,YStart+6,0,green)
    Draw[btn[6]](XStart + XDiff*4+ 2,YStart+2,green)
    Draw[btn[7]](XStart + XDiff*4+ 6,YStart+2,green)
    Draw[btn[8]](XStart + XDiff*4+10,YStart+2,green)


--Highlight button
    if within( BtnY , 1 , 9 ) then
        if within( BtnX , 1 , 4 ) then
            local LowX=BtnX*XDiff +XStart -4
            local LowY=BtnY*YDiff +YStart -3
            gui.box(LowX,LowY,LowX+XDiff-2,LowY+YDiff-2,blue,blue)

--Handle text. Feels sort of hard-coded, however
            gui.text(1,160,BasicText[BtnX])

            local adv
            if BtnY == 9 then
                adv= 10
            elseif Status[BtnX][BtnY] == "???" then
                adv=  9
            elseif within(BtnX,1,2) and ((Status[3-BtnX][BtnY] == "???")) then
                adv=  9
            elseif within(BtnX,1,2) then
                adv=  1
                if Status[2][BtnY] == "No" then
                    adv= adv + 1
                end
                if Status[1][BtnY] == "No" then
                    adv= adv + 2
                end
            else
                adv= 5
                if BtnX == 4 then
                    adv= adv+2
                end
                if Status[BtnX][BtnY] == "No" then
                    adv= adv+1
                end
            end
            gui.text(1,175,AdvText[adv][1])
            gui.text(1,183,AdvText[adv][2])
        end
    end


--Lines!
    for i= 1, 5 do
        local Xpos= XStart + XDiff*i -5
        gui.line(Xpos,0,Xpos,YStart + YDiff*10-4,green)
    end

    for j= 1, 9 do
        local Ypos= YStart + YDiff*j -4
        gui.line( 0, Ypos, XStart + XDiff*5-5, Ypos, green)
    end
    gui.line(XStart+XDiff-4, YStart + YDiff*10-4, XStart + XDiff*5-5, YStart + YDiff*10-4, green)


--Button labels!
    for j= 1, 8 do
        local Ypos= YStart + YDiff*j
        gui.text( 1, Ypos, btn[j])
        Draw[btn[j]](XStart + XDiff - 12, Ypos, green)

        for i= 1, 4 do
            gui.text(XStart + XDiff*i, Ypos, Status[i][j])
        end
    end

    for i= 1, 4 do
        gui.text(XStart + XDiff*i, YStart + YDiff*9, "All")
    end

end


--*****************************************************************************
function CatchInput()
--*****************************************************************************
-- For use with registerbefore. It will get the input and paste it into
-- the input list.

    fc= movie.framecount()
    for pl= 1, players do
        if BufLen[pl] > 0 then
            table.remove(BufInput[pl],1)
            table.insert(BufInput[pl],InputList[pl][fc-1])
        end
        local NewJoy= JoyToNum(joypad.get(pl))
        InputList[pl][fc-1]= NewJoy
        if not BackupList[pl][fc-1]  or  (BackupOverride[pl] == "write") then
            BackupList[pl][fc-1]= NewJoy
        end
    end
    SetInput()
end
emu.registerbefore(CatchInput)


--*****************************************************************************
function OnLoad()
--*****************************************************************************
-- For use with registerload. It updates ThisInput so we're not stuck with
-- junk being carried over when we load something.
-- Also clears whatever rewind stuff is stored.

    InputSnap()
    for pl= 1, players do    --Assume user is done messing with backup on load
        BackupOverride[pl]= false
    end

    fc= movie.framecount()
    LastLoad= fc
    saveCount= 0
    SetInput()
    ApplyInput() -- Sanity versus unpaused stateload
end
savestate.registerload(OnLoad)


--*****************************************************************************
function ItIsYourTurn()
--*****************************************************************************
-- For use with gui.register. I need to catch input while paused!
-- Needed for a half-decent interface.

--Ensure things are nice, shall we?
    local fc_= movie.framecount()
    lastkeys= keys
    keys= input.get()

    if fc ~= fc_ then -- Sanity versus unusual jump (Reset cycle?)
        OnLoad()
    end

--Process Rewind
 if saveMax > 0 then  -- Don't process if Rewind is disabled
    if pressrepeater(rewind) or rewinding then
        rewinding= false
        if RewindThis() then
            InputSnap()
            movie.rerecordcounting(true)
            fc= movie.framecount()
            SetInput()
            if saveCount < 1 then  emu.pause()  end
        end
    end
 end

--Should we bother the backups today?
    for pl= plmin, plmax do
        if press(BackupReadSwitch) then
                if BackupOverride[pl] ~= "read" then
                    BackupOverride[pl]= "read"
                else
                    BackupOverride[pl]= false
                end
        end
        if press(BackupWriteSwitch) then
            if BackupOverride[pl] ~= "write" then
                BackupOverride[pl]= "write"
            else
                BackupOverride[pl]= false
            end
        end
    end

    if keys[BackupReadSwitch] and keys[BackupWriteSwitch] then
        gui.text(30,1,"Hey, quit holding both buttons!")
    elseif keys[BackupReadSwitch] then
        gui.text(30,1,"This lets the script revert to the backup.")
    elseif keys[BackupWriteSwitch] then
        gui.text(30,1,"This stores input into the backup.")
    end

--Switch players on keypress
    if press(PlayerSwitch) then
        if plmin ~= plmax then
            plmax= 1
        elseif plmin == players then
            plmin= 1
        else
            plmin= plmin+1
            plmax= plmax+1
        end
    end

--Display which player we're selecting. Upperleft corner seems good.
    if plmin == plmax then
        gui.text(1,2,plmin)
        gui.text(11,2,BufLen[plmin])
    else
        gui.text(1,2,"A")
    end

--Check if we want to see control options
    if keys[opt] then
        gui.opacity(1)
        ControlOptions()
        SetInput()

--Otherwise, handle what we can see
    else

--Inserts and deletes.
--table functions don't work well on tables that don't connect from 1.
        if pressrepeater(Insert) then
            for pl= plmin, plmax do
                BufLen[pl]= BufLen[pl] + 1
                if BufLen[pl] > 0 then
                    table.insert(BufInput[pl],1,0)
                end
            end
            SetInput()
        end
        if pressrepeater(Delete) then
            for pl= plmin, plmax do
                if BufLen[pl] > 0 then
                    table.remove(BufInput[pl],1)
                end
                BufLen[pl]= BufLen[pl] - 1
            end
            SetInput()
        end

--Script key handler
        for pl= plmin, plmax do
            for i= 1, 8 do
                if press(key[i]) and ScriptEdit[pl][i] then
                    if ThisInput[pl][btn[i]] then
                        ThisInput[pl][btn[i]]= FalseSwitch[pl][i]
                    else
                        ThisInput[pl][btn[i]]= TrueSwitch[pl][i]
                    end
                end
            end
        end

        DisplayInput()
    end

--Last bits of odds and ends
    ApplyInput()

    collectgarbage("collect")
end
gui.register(ItIsYourTurn)


--*****************************************************************************
function Rewinder()
--*****************************************************************************
-- Contains the saving part of the Rewind engine.
-- Original by Antony Lavelle, added by DarkKobold, messed by FatRatKnight
 if saveMax > 0 then  -- Don't process if Rewind is disabled
    if keys[rewind] and saveCount > 0 then
        rewinding= true

    elseif (fc - LastLoad)%SaveBuf == 0 then
        if saveCount >= saveMax then
            table.remove(saveArray,1)
        else
            saveCount= saveCount+1
        end

        if saveArray[saveCount] == nil then
            saveArray[saveCount]= savestate.create();
        end

        savestate.save(saveArray[saveCount]);

        movie.rerecordcounting(false)
    end
 end

end

--emu.registerafter(Rewinder)








local TestNumOfDoom= 0
local TestFile= io.open("Backup" .. TestNumOfDoom .. ".txt" , "r")
while TestFile do
    TestFile:close()
    TestNumOfDoom= TestNumOfDoom+1
    if TestNumOfDoom >= BackupFileLimit then break end
    TestFile= io.open("Backup" .. TestNumOfDoom .. ".txt" , "r")
end




local FCEUXbtn= {"right", "left", "down", "up", "start", "select", "B", "A"}
local FCEUXfrm= {"R","L","D","U","T","S","B","A"}
local FCEUXorder= {}

for i= 1, 8 do
    for j= 1, 8 do
        if btn[i] == FCEUXbtn[j] then
            FCEUXorder[j]= i
            break
        end
    end
end

--*****************************************************************************
function SaveToFile()
--*****************************************************************************
-- Creates a file that, effectively, stores whatever's in the script at the
-- time.
-- List of known glitches:
--   It only reads player 1 when deciding when to start or stop.

    InputSnap()
    local LatestIndex= 0
    local EarliestIndex= math.huge
    for Frame, Pads in pairs(InputList[1]) do
        LatestIndex= math.max(LatestIndex,Frame)
        EarliestIndex= math.min(EarliestIndex,Frame)
    end


    local BFile = io.open("Backup" .. TestNumOfDoom .. ".txt" , "w")
    BFile:write("StartFrame: " .. EarliestIndex .. "\n")

    for Frame= EarliestIndex, LatestIndex do
        BFile:write("|0")
        for pl= 1, 4 do
            BFile:write("|")
            if InputList[pl] then
                if InputList[pl][Frame] then
                    local Pads= InputList[pl][Frame]
                    for i= 1, 8 do
                        if ReadJoynum(Pads,FCEUXorder[i]) then
                            BFile:write(FCEUXfrm[i])
                        else
                            BFile:write(".")
                        end
                    end
                else
                    BFile:write("        ")
                end
            end
        end
        BFile:write("\n")
    end
    BFile:close()

end

emu.registerexit(SaveToFile)











emu.pause()

while true do
    Rewinder()

    emu.frameadvance()
end
