/*
|------------------------|
|               The mIRC |
| BeFunge'93 Interpreter |
|    by argv[0] aka Jinx |
|------------------------|
|           version 1.1b |
|------------------------|

- For usage instructions please visit http://www.jinxdesign.net/befunge
- Feel free to modify this source, but don't post it without my permission

*/

alias befunge {
  %funge.cwidth = $width(A,fixedsys,9) + 1 
  %funge.cheight = %funge.cwidth * 2
  %funge.dwidth = 80
  %funge.dheight = 25
  if (%funge.speed == $null) %funge.speed = 2
  %oldpos.x = 0
  %oldpos.y = 0
  %pos.x = 0
  %pos.y = 0
  ; Data file
  ;
  if ($fopen(befunge)) fclose befunge
  .fopen -n befunge ~befunge.bf.tmp
  clearfungedata

  ; Window setup
  ;
  window -dpf +l @BeFunge-Editor 100 100 $calc(%funge.cwidth * %funge.dwidth) $calc(%funge.cheight * %funge.dheight) fixedsys 9
  clearfungewindow
  refreshwindow
}

on 1:KEYDOWN:@BeFunge-Output:*: { 
  if (%funge.isrunning) {
    ; display it and add it to the text stack
    if ($keyval == 13) {
      inc %funge.outline
      aline @BeFunge-Output 
    }
    elseif (($keyval isnum 37-40) || ($keyval < 32)) {
      return
    }
    else arline @BeFunge-Output %funge.outline $replace($keychar, $chr(32), $chr(160))

    ; text stack
    %funge.inputval = %funge.inputval $iif($asc($keychar) == $null, 32, $replace($asc($keychar),13,10))

    ; handle event
    if (%funge.waitinput == text) {
      ; pop the text stack
      push $popin(text)
      endinput
    }
    elseif ((%funge.waitinput == number) && ($keyval == 13)) {
      ; collapse the text stack into a number
      push $popin(number)
      endinput
    }
  }
}

alias popin {
  var %i = 0, %r, %max
  if ($1 == text) {
    %r = $gettok(%funge.inputval, 1, 32)
    %funge.inputval = $deltok(%funge.inputval, 1, 32)
    return %r
  }
  elseif ($1 == number) {
    %max = $numtok(%funge.inputval,32) - 1
    while (%i < %max) {
      inc %i
      %r = %r $+ $chr($gettok(%funge.inputval,%i,32))
    }
    %funge.inputval = $null
    return $int(%r)
  }
}

on 1:CLOSE:@BeFunge-Output: {
  if (%funge.waitinput == text) {
    push 10
    endinput
  }
  elseif (%funge.waitinput == number) {
    push $iif(%funge.inputval != $null, %funge.inputval, 0)
    endinput
  }
  if (%funge.isrunning) openoutputwindow
}

on 1:CLOSE:@BeFunge-Stack: {
  if (%funge.isrunning) openstackwindow
}

alias startinput {
  titlebar @BeFunge-Editor $window(@BeFunge-Editor).title [WAITING FOR $upper($$1) INPUT]
  %funge.waitinput = $$1
  .timerRUNFUNGE off
  window -a @BeFunge-Output
  ;window -adf +l @BeFunge-Input $calc($window(@BeFunge-Editor).x + ($window(@BeFunge-Editor).dw / 2) - 100) $calc($window(@BeFunge-Editor).y + ($window(@BeFunge-Editor).dh / 2) - 50) 200 100
  titlebar @BeFunge-Output [TYPE HERE]
  flash @BeFunge-Output
}
alias endinput {
  titlebar @BeFunge-Editor $remove($window(@BeFunge-Editor).title, [WAITING FOR $upper(%funge.waitinput) INPUT])
  %funge.waitinput = $false
  titlebar @BeFunge-Output 
  fungespeed %funge.speed
}

on 1:KEYUP:@BeFunge-Editor:*: { 
  .timerEDITKEY off
}

on 1:KEYDOWN:@BeFunge-Editor:*: { 
  if (%funge.isrunning) return
  addkey $keyval $keychar
  .timerEDITKEY -m 1 500 startaddkey $keyval $keychar 
}

on 1:KEYUP:@BeFunge-Editor:*: { 
  .timerEDITKEY off
}

on 1:CLOSE:@BeFunge-Editor: {
  if (%funge.isrunning) stopfunge
  if ($fopen(befunge)) .fclose befunge
  if ($window(@BeFunge-Stack)) window -c @BeFunge-Stack
  if ($window(@BeFunge-Output)) window -c @BeFunge-Output
  if ($window(@BeFunge-Input)) window -c @BeFunge-Input
  .remove ~befunge.bf.tmp
}

; /addkey $keyval $keychar 
alias addkey {
  echo -s $1-
  %oldpos.x = %pos.x
  %oldpos.y = %pos.y
  if ($1 isnum 37-40) {
    if ($1 == 37) {
      dec %pos.x
    }
    elseif ($1 == 38) {
      dec %pos.y
    }
    elseif ($1 == 39) {
      inc %pos.x
    }
    elseif ($1 == 40) {
      inc %pos.y
    }
    if (%pos.x < 0) %pos.x = %funge.dwidth - 1
    elseif (%pos.x >= %funge.dwidth) %pos.x = 0
    if (%pos.y < 0) %pos.y = %funge.dheight - 1
    elseif (%pos.y >= %funge.dheight) %pos.y = 0
    refreshwindow
    return
  }
  elseif ($1 >= 32) {
    clearbox %pos.x %pos.y
    if ($1 > 32) drawtext @BeFunge-Editor $color(text) $calc(%funge.cwidth * %pos.x + 1) $calc(%funge.cheight * %pos.y + 1) $2
    seektopos
    bset &c 1 $iif(!$asc($2), 32, $asc($2))
    .fwrite -b befunge &c
  }
  if ($1 == 8) {
    dec %pos.x 
    if (%pos.x < 0) {
      dec %pos.y
      %pos.x = %funge.dwidth - 1
    }
    else {
      .fseek befunge $calc(%funge.dwidth * %pos.y + %pos.x)
      bset &c 1 32
      .fwrite -b befunge &c
    }
    if (%pos.y < 0) {
      %pos.x = 0
      %pos.y = 0
      beep 1
    }
    clearbox %pos.x %pos.y
  }
  if ($1 >= 32 || $1 == 13) {
    inc %pos.x
    if (%pos.x > %funge.dwidth || $1 == 13) {
      inc %pos.y
      %pos.x = 0
    }
  }
  refreshwindow
}
alias startaddkey { .timerEDITKEY -m 0 800 addkey $1- }

alias refreshwindow {
  drawbox $color(background) %oldpos.x %oldpos.y
  drawbox $iif(%funge.isrunning, 4, 2) %pos.x %pos.y
  var %debug 
  if (%funge.isrunning) %debug = (J: $+ $iif(%funge.jump,1,0) S: $+ $iif(%funge.smode,1,0) $+ )
  titlebar @BeFunge-Editor $iif(%funge.isrunning, [RUN MODE], [EDIT MODE]) (position: %pos.x $+ , %pos.y $+ ) %debug 
}

alias drawbox {
  var %left = $2 * %funge.cwidth
  var %top = $3 * %funge.cheight
  drawrect @BeFunge-Editor $1 1 %left %top $calc(%funge.cwidth + 1) $calc(%funge.cheight + 1)
}
alias clearbox {
  var %left = $$1 * %funge.cwidth
  var %top = $$2 * %funge.cheight
  drawrect -f @BeFunge-Editor $color(background) 1 $calc(%left + 1) $calc(%top + 1) $calc(%funge.cwidth - 1) $calc(%funge.cheight - 1)
}
alias clearfungedata {
  .fseek befunge 0
  bset &data $calc(%funge.dwidth * %funge.dheight) 32
  breplace &data 0 32
  .fwrite -b befunge &data
}
alias clearfungewindow {
  clear @BeFunge-Editor
  var %i = 0
  while (%i < %funge.dwidth) {
    inc %i
    ;drawline @BeFunge-Editor 14 1 $calc(%i * %funge.cwidth) 0 $calc(%i * %funge.cwidth) $window(@BeFunge-Editor).dh
    ;drawline @BeFunge-Editor 14 1 0 $calc(%i * %funge.cheight) $window(@BeFunge-Editor).dw $calc(%i * %funge.cheight) 
  }
}

alias openstackwindow {
  window -adlf +l @BeFunge-Stack $calc($window(@BeFunge-Editor).x + $window(@BeFunge-Editor).w) $window(@BeFunge-Editor).y 100 $window(@BeFunge-Editor).dh
}
alias openoutputwindow {
  window -adf +l @BeFunge-Output $window(@BeFunge-Editor).x $calc($window(@BeFunge-Editor).y + $window(@BeFunge-Editor).h) $window(@BeFunge-Editor).dw 100
}

alias changeposition {
  if (%funge.isrunning) return
  %oldpos.x = %pos.x
  %oldpos.y = %pos.y
  %pos.x = $int($calc($$1 / %funge.cwidth))
  %pos.y = $int($calc($$2 / %funge.cheight))
  refreshwindow
}
alias seektopos {
  .fseek befunge $calc(%funge.dwidth * $iif($1 != $null,$2,%pos.y) + $iif($1 != $null,$1,%pos.x))
  ;if ($1 != $null) echo 2 -s * seek to position $1 $2 $calc(%funge.dwidth * $iif($1 != $null,$2,%pos.y) + $iif($1 != $null,$1,%pos.x))
}

menu @BeFunge-Editor {
  sclick:changeposition $mouse.x $mouse.y
  Clear: { clearfungedata | clearfungewindow }
  -
  Paste:/pastefunge
  -
  Load...:/loadfunge
  Save As...:/savefunge
  -
  $iif($timer(RUNFUNGE) != $null && %funge.isrunning, >) Run! (F7):/runfunge
  $iif(%funge.isrunning, $iif($timer(RUNFUNGE) == $null && %funge.isrunning, >) Break (F9)):/fungebreak
  $iif(%funge.isrunning, Step instruction (F8)):/stepfunge
  $iif(%funge.isrunning, Stop):/stopfunge
  -
  Set Speed
  .$iif(%funge.speed == 0, >) Pause { fungespeed 0 }
  .$iif(%funge.speed == 1, >) 1 { fungespeed 1 }
  .$iif(%funge.speed == 2, >) 2 { fungespeed 2 }
  .$iif(%funge.speed == 3, >) 3 { fungespeed 3 }
  .$iif(%funge.speed == 4, >) 4 { fungespeed 4 }
  .$iif(%funge.speed == 5, >) 5 { fungespeed 5 }
}

alias runfunge {
  if (!%funge.isrunning) {
    syncfungefile
    .copy -o ~befunge.bf.tmp ~befunge.bf-run.tmp
    .fclose befunge
    .fopen befunge ~befunge.bf-run.tmp
    %oldpos.x = %pos.x
    %oldpos.y = %pos.y
    %pos.x = 0
    %pos.y = 0
    %funge.smode = $false
    %funge.jump = $false
    %funge.outline = 1
    %funge.direction = 2
    %funge.waitinput = $false
    unset %funge.inputval
    refreshwindow 4
    openstackwindow
    openoutputwindow
    window -a @BeFunge-Editor
    clear @BeFunge-Output
    clear @BeFunge-Stack
    if ($window(@BeFunge-Input)) clear @BeFunge-Input
  }
  %funge.isrunning = $true
  if (%funge.speed = 5) {
    steptoend
  }
  elseif (%funge.speed > 1) {
    .timerRUNFUNGE -m 0 $calc(200 * $calc(4 - %funge.speed)) stepfunge
  }
}
alias stopfunge {
  .timerRUNFUNGE off
  %funge.direction = 0
  unset %funge.smode
  unset %funge.jump
  unset %funge.isrunning
  unset %funge.waitinput
  titlebar @BeFunge-Output
  if ($1 != -nosync) {
    %oldpos.x = %pos.x 
    %oldpos.y = %pos.y
    %pos.x = 0
    %pos.y = 0
  }
  refreshwindow
  drawbox 2 %pos.x %pos.y
  .fclose befunge
  .remove ~befunge.bf-run.tmp
  .fopen befunge ~befunge.bf.tmp
  if ($1 != -nosync) syncfungefile
}
alias fungespeed {
  %funge.speed = $$1
  if (%funge.isrunning) {
    if (%funge.speed == 5) steptoend
    elseif (%funge.speed > 1) .timerRUNFUNGE -m 0 $calc(200 * $calc(4 - %funge.speed)) stepfunge
    else .timerRUNFUNGE off
  }
}
alias fungebreak {
  .timerRUNFUNGE off
}


alias push {
  if ($1 == $null) return
  iline @BeFunge-Stack 1 $1 $iif($1 >= 32,' $+ $chr($1) $+ ', '?')
}
alias pop {
  var %r = $look(1)
  dline @BeFunge-Stack 1
  return %r
}
alias look {
  return $gettok($line(@BeFunge-Stack,$$1),1,32)
}
alias steptoend {
  while ((%funge.isrunning) && (!%funge.waitinput)) stepfunge
}
alias stepfunge {
  if (%funge.waitinput) return
  if (!%funge.isrunning) { runfunge | .timerRUNFUNGE off }
  seektopos
  var %acmd = $fgetc(befunge)
  var %cmd = $chr(%acmd)
  var %t, %b = $look(1), %c = $look(2), %d = $look(3)
  if (%funge.smode && %cmd != ") {
    push %acmd
    goto move
  }
  if (%funge.jump) {
    if (%cmd != #) %funge.jump = $false
    goto move
  }
  elseif (%cmd == ") { 
    %funge.smode = $iif(%funge.smode, $false, $true)
  }
  elseif (%acmd == 35) { 
    %funge.jump = $true
  }
  elseif (%cmd == ^) %funge.direction = 1
  elseif (%cmd == >) %funge.direction = 2
  elseif (%cmd == v) %funge.direction = 3
  elseif (%cmd == <) %funge.direction = 4
  elseif (%cmd == ?) %funge.direction = $rand(1,4)
  elseif (%cmd == [) {
    dec %funge.direction 
    if (%funge.direction <= 0) %funge.direction = 4
  }
  elseif (%cmd == ]) {
    inc %funge.direction
    if (%funge.direction > 4) %funge.direction = 1
  }
  elseif (%cmd === B) fungebreak
  elseif (%cmd == @) {
    var %x = %pos.x, %y = %pos.y 
    stopfunge -nosync
    refreshwindow
    return
  }
  elseif (%cmd isin +-*/%) { 
    %t = $int($calc(%c %cmd %b))
    pop
    pop
    push %t
  }
  elseif (%cmd == !) { 
    if (%b) %t = 0
    else %t = 1
    pop
    push %t
  }
  elseif (%cmd == `) { 
    if (%c > %b) %t = 1
    else %t = 0
    pop
    pop
    push %t
  }
  elseif (%cmd == |) { 
    pop
    if (%b) %funge.direction = 1
    else %funge.direction = 3
  }
  elseif (%cmd == _) {
    pop
    if (%b) %funge.direction = 4
    else %funge.direction = 2
  }
  elseif (%cmd == .) { 
    arline @BeFunge-Output %funge.outline %b $+ $chr(160)
    pop
  }
  elseif (%cmd == ,) { 
    if (%b == 10) {
      inc %funge.outline
    }
    elseif (%b >= 32) {
      arline @BeFunge-Output %funge.outline $replace($chr(%b),$chr(32),$chr(160))
    }
    pop
  }
  elseif (%cmd == &) { 
    if ((%funge.inputval != $null) && ($gettok(%funge.inputval, -1, 32) == 10)) {
      push $popin(number)
    }
    else startinput number
  }
  elseif (%cmd == ~) { 
    if (%funge.inputval != $null) {
      push $popin(text)
    }
    else startinput text
  }
  elseif (%cmd == :) { 
    push %b
  }
  elseif (%cmd == $) { 
    pop
  }
  elseif (%cmd == \) { 
    %t = %b
    var %v = %c
    pop
    pop
    push %t
    push %v
  }
  elseif (%cmd == g) { 
    seektopos %c %b
    var %chr = $fgetc(befunge)
    %t = $iif(%chr != $null, %chr, 32)
    pop 
    pop
    push %t
  }
  elseif (%cmd == p) { 
    if (%d != $null) {
      seektopos %c %b
      bset &data 1 %d
      .fwrite -b befunge &data
      clearbox %c %b
      if (%d > 32) drawtext @BeFunge-Editor 1 $calc(%funge.cwidth * %c + 1) $calc(%funge.cheight * %b + 1) $chr(%d)
      elseif (%d != 32) drawtext @BeFunge-Editor 1 $calc(%funge.cwidth * %c + 1) $calc(%funge.cheight * %b + 1) ?
    }
    pop
    pop
    pop
  }
  if (%cmd isincs abcdef || %cmd isnum 0-9) {
    push $base(%cmd,16,10)
  }
  :move
  %oldpos.x = %pos.x
  %oldpos.y = %pos.y
  if (%funge.direction == 1) {
    dec %pos.y
    if (%pos.y < 0) %pos.y = %funge.dheight - 1
  }
  elseif (%funge.direction == 2) {
    inc %pos.x
    if (%pos.x >= %funge.dwidth) %pos.x = 0
  }
  elseif (%funge.direction == 3) {
    inc %pos.y
    if (%pos.y >= %funge.dheight) %pos.y = 0
  }
  elseif (%funge.direction == 4) {
    dec %pos.x 
    if (%pos.x < 0) %pos.x = %funge.dwidth - 1
  }
  refreshwindow 
}



alias f7 { runfunge }
alias f8 { if (%funge.isrunning && $timer(RUNFUNGE) == $null) stepfunge }
alias f9 { fungebreak }
alias loadfunge {
  if (%funge.isrunning) return
  var %f = $+(",$sfile($mircdir),")
  ;if ($file(%f).size < $calc(%funge.dwidth * %funge.dheight)) return
  .fclose befunge
  .remove ~befunge.bf.tmp
  .copy -o %f ~befunge.bf.tmp
  .fopen befunge ~befunge.bf.tmp
  syncfungefile
}
alias savefunge {
  .copy -o ~befunge.bf.tmp $$?="Enter filename"
}
alias syncfungefile {
  if (%funge.isrunning) return
  if ($fopen(befunge) == $null) return
  var %c
  clearfungewindow
  %pos.x = 0
  %pos.y = 0
  seektopos
  while (!$feof && !$ferr) {
    %c = $fgetc(befunge)
    if (%c > 32 && %c <= 126) drawtext @BeFunge-Editor 1 $calc(%funge.cwidth * %pos.x + 1) $calc(%funge.cheight * %pos.y + 1) $chr(%c)
    elseif (%c != 32) drawtext @BeFunge-Editor 1 $calc(%funge.cwidth * %pos.x + 1) $calc(%funge.cheight * %pos.y + 1) ?
    inc %pos.x
    if (%pos.x >= %funge.dwidth) {
      %pos.x = 0
      inc %pos.y
    }
  }
  %pos.x = 0
  %pos.y = 0
  %oldpos.x = 0
  %oldpos.y = 0
  seektopos
  refreshwindow
}

alias pastefunge {
  if (%funge.isrunning) return
  var %i = 1
  var %startx = %pos.x
  while ($cb(%i)) {
    %pos.x = %startx
    var %x = 1
    var %line = $ifmatch
    while ($mid(%line, %x, 1) != $null) {
      var %c = $ifmatch
      %oldpos.x = %pos.x
      %oldpos.y = %pos.y
      clearbox %pos.x %pos.y
      if ($ifmatch != $chr(32)) drawtext @BeFunge-Editor $color(text) $calc(%funge.cwidth * %pos.x + 1) $calc(%funge.cheight * %pos.y + 1) %c
      seektopos
      bset &c 1 $iif(!$asc(%c), 32, $asc(%c))
      .fwrite -b befunge &c
      inc %pos.x
      inc %x
      refreshwindow
    }
    %oldpos.x = %pos.x
    %oldpos.y = %pos.y
    inc %pos.y
    refreshwindow
    inc %i
  }
  %oldpos.x = %pos.x
  %oldpos.y = %pos.y
  dec %pos.y
  refreshwindow
}

alias arline {
  if ($3 == $null) return
  $iif($line($$1,$$2) == $null, a, r) $+ line -p $$1 $iif($line($$1,$$2) != $null, $$2) $line($$1,$$2) $+ $3-
}
