require_relative "./escape.rb"
require_relative "./type.rb"
require_relative "./version.rb"

class Op < Struct.new(:name, :parse_nargs, :block, :ways)
  def dup
    Op.new(name.dup, parse_nargs, block, ways.map(&:dup))
  end
  def dup_mod(mod)
    if mod == 0 && ways.any?{|way| way.mod < 0 }
      r = dup
      r.ways.filter!{|way| way.mod >= 0 }
      return r
    end

    return self if mod <= 0
    r = dup

    r.ways.filter!{|way| way.can_mod? || way.is_overload }

    r.ways.each{|w|
      w.mod += mod if w.can_mod?
    }
    r
  end
end

class Way < Struct.new(:name, :arg_ranks, :ret_rank, :arg_types, :ret_type, :mod, :vectorize, :promote, :special_reps, :stars, :impl)
  def mod_arg_ranks
    arg_ranks.map{|r| r < 0 ? ~r + mod : r }
  end
  def mod_ret_rank
    ret_rank < 0 ? ~ret_rank + mod : ret_rank
  end
  def dup
    Way.new(name, arg_ranks.dup, ret_rank, arg_types.dup, ret_type, mod, vectorize, promote, special_reps, stars, impl)
  end
  def can_mod?
    (arg_ranks + [ret_rank]).any?{|r| r < 0 }
  end
  def is_overload
    !stars.compact.empty?
  end
end

def lookup_op(name)
  (0..).each{|mod|
    op = Ops[name]

    return nil if op && mod>0 && !op.ways.any?(&:vectorize)
    if op
      return op.dup_mod(mod)
    else
      return nil if !name[/,$/]
      name = $`
    end
  }
end

def new_op(name, type_sig, block: false, vectorize: true, promote: true, &impl)
  parts = type_sig.gsub("->"," ").split
  types = parts.map{|s|s.sub(/_.*/,'') }

  *arg_ranks, ret_rank = types.map{|arg| spec_rank(arg) }
  *arg_types, ret_type = types.map{|arg| spec_type(arg) }
  special_reps = types[0...-1].map{|arg| is_special_rep(arg) }

  stars = parts[0...-1].map{|arg| arg[/_(\d)/] ? $1.to_i : nil }

  mod = 0
  mod -=1 if arg_types.include? AIntSpec

  way = Way.new(name, arg_ranks, ret_rank, arg_types, ret_type, mod, vectorize, promote, special_reps, stars, impl)
  Op.new(name, arg_ranks.size, block, [way])
end

Ops = {}
OpExamples = {}
NoCommaOverloads = []
OpsUsingZero = []

if !defined? mk_op
  def mk_op(names, type_sig, example="", block: false, vectorize: true, promote: true, uses_zero: false, example2: nil, type2: nil, &impl) # type2 is for show only implemented by internal op
    names = names.sub(/ *\(.*\)$/,'')
    if example2
      OpExamples[names] = [example, example2]
    elsif example != ""
      OpExamples[names] = example
    end
    names = names.split
    NoCommaOverloads.concat names.filter{|name| name.size == 1} if names.any?{|name| name[/,$/] }
    OpsUsingZero << names[-1] if uses_zero

    op = new_op(names[-1], type_sig, block: block, vectorize: vectorize, promote: promote, &impl)

    names.each{|name|
      if Ops[name]
        Ops[name].ways << op.ways[0]
      else
        Ops[name] = op.dup
      end
    }

    if op.ways[0].can_mod? && op.ways[0].vectorize
      mod_op = op.dup
      mod_op.ways[0].mod = 1

      names.each{|name|
        if name =~ /^[a-z]/ && op.ways[0].arg_types.all?{|at| at != ACharSpec }
          name = name.sub(/^./){|first|first.upcase}
          if Ops[name]
            Ops[name].ways << mod_op.ways[0]
          else
            Ops[name] = mod_op.dup
          end
        end
      }
    end
  end

  def category(name); end
  def quickref_op(names, desc, example)
    OpExamples[names] = example if example != ""
  end
end

NotFirst = :not_first

category("numeric ops")
mk_op("+ add", "int x -> x", '1 2+ -> 3', type2: "char int -> char", example2: "'a 1+ -> 'b'", &add=->a,b{ a.value+b.value })
mk_op("- sub (^ for char char)", "x int -> x", '5 3- -> 2', example2: "'c 'a ^ -> 2", type2: "char char -> int", &subt=->a,b{ a.value-b.value })
mk_op("* mult", "int int -> int", '2 3* -> 6'){|a, b| a.value == 0 ? 0 : a.value*b.value }
mk_op("/ div", "int int -> int", '7 2/ -> 3'){|a, b| b.value == 0 ? 0 : a.value/b.value }
mk_op("% mod", "x int -> int", '7 2% -> 1'){|a, b| a.value == 0 ? 0 : b.value == 0 ? a.value : a.value%b.value }
mk_op("^ pow", "int int -> int", '2 3^ -> 8'){|a, b| pow(a, b) }
mk_op("~ negate", "int -> int", '1~ -> -1'){|a| -a.value }
mk_op("( pred", "x -> x", '4( -> 3'){|a| a.value-1 }
mk_op(") succ", "x -> x", "'a) -> 'b'"){|a| a.value+1 }
mk_op("} countTo", "int -> [int]", "3} -> [1,2,3]"){|a| range(1,a.value+1) }
mk_op("{ wholes", "[int]", "{ -> [0,1,2,..."){ range_from(0) }
mk_op("_ sum", "[int] -> int", '1,2,3,4 _ -> 10', promote: false){|a| to_eager_list(a).sum }
mk_op(". prod", "[int] -> int", '1,2,3,4 . -> 24', promote: false){|a| product(a) }

category('char ops (and <a href="ops.html#asubcharsubops">a<sub>char</sub></a> ops)')
mk_op("~ read", "[char] -> int", '"23"~ -> 23'){|a| read_num(a)[0] }
mk_op("} readAll", "[char] -> [int]", '"1,2 3"} -> [1,2,3]'){|a| split_non_digits(a) }
mk_op(". ord", "char -> int", "'d. -> 100"){|a| a.value }
mk_op("., chr", "int -> char", "100., -> 'd'"){|a| a.value }
mk_op("` str", "int -> [char]", '5` -> "5"'){|a| str(a) }
mk_op("` strip", "[achar] -> [achar]", '" a b\n"` -> "a b"', &strip=->a{ strip(a,$at) })
mk_op("* replicate (^, for aint)", "[achar] int -> [achar]", '"ab"3* -> "ababab"', &replicate=->a, b { concat(take(b.value,repeat(a).const).const) })
mk_op("* join", "[[achar]] {[achar]} -> [achar]", '1,2,3 " "* -> "1 2 3"', &join=->a, b{ join(a,b) })
mk_op("/ rightJustify", "[achar] int -> [achar]", '"hi" 3 / -> " hi"', uses_zero: true, &rjust=->a,b{
  z = zero($rank,$at).const
  append(take(b.value - len(a.value),repeat(z).const).const, a)
})
mk_op("/ split (%, for aint)", "[achar] [achar] -> [[achar]]", '"a..b.c." "."/ -> ["a","b","c"]', &split=->a,b{ s=cut(a,repeat(b).const).const; filter(s,s,nil) })
mk_op("_ words", "[achar] -> [[achar]]", '"ab c\n d" _ -> ["ab","c","d"]', &words=-> a{ at = $at; x=promise{ group_while(a,a,at) }; filter(x,x,at) })
mk_op("% isCharClass", "char [char] -> int", '"Hello!" \'a % -> [0,1,1,1,1,0]'){|a,b| is_char_class(a,b) }

category("generic ops")
mk_op("a append", "[a] [a] -> [a]", '"ab" "cd"a -> "abcd"'){|a, b| append(a,b) }
mk_op("b backwards reverse", "[a] -> [a]", '"abc" b -> "cba"', promote: false){|a| reverse(a) }
mk_op("c cons", "[a] a -> [a]", '1,2 0c -> [0,1,2]'){|a, b| [b, a] }
mk_op("h head", "[a] -> a", '"abc"h -> \'a\'', promote: false, uses_zero: true){|a| z=zero($rank,$at); a.empty ? z : a.value[0].value }
mk_op("j just", "a -> [a]", "1j -> [1]"){|a|[a, [].const]}
mk_op("l last", "[a] -> a", '"abc"l -> \'c\'', promote: false, uses_zero: true){|a| z=zero($rank,$at); last(a, z.const) }
mk_op("n not", "a -> int", '0,1,2n -> [1,0,0]', promote: false){|a| truthy($at, a.value) ? 0 : 1 }
mk_op("o cons0 consDefault", "[a] -> [a]", '"abc"o -> " abc"', uses_zero: true){|a| [zero($rank,$at).const, a] }
mk_op("p pivot transpose", "[[a]] -> [[a]]", '"abc","def"p -> ["ad","be","cf"]', promote: false, uses_zero: true){|a| transpose(a, $at, $rank) }
mk_op("q equal", "a a -> int", "1,2,3 1q -> [1,0,0]"){|a,b| spaceship(a, b) == 0 ? 1 : 0 }
mk_op("r repeat", "a -> [a]", "1r -> [1,1,1,1,1,1..."){|a| repeat(a) }
mk_op("s size len length", "[a] -> int", '"abc"s -> 3', promote: false){|a| len(a.value) }
mk_op("t tail", "[a] -> [a]", '"abc"t -> "bc"', promote: false){|a| a.empty ? [] : a.value[1].value }
mk_op("u unite concat", "[[a]] -> [a]", '"ab","cd","e"u -> "abcde"', promote: false){|a| concat(a) }
mk_op("v vet filter", "[a] [b] -> [a]", '"abcdef":2%v -> "ace"'){|a,b| filter(a,b,$bt) }
mk_op("w while takeWhile", "[a] [b] -> [a]", '"abc def":w -> "abc"'){|a,b| take_while(a,b,$bt) }
mk_op("x max", "a a -> a", '"bc" "ad" x -> "bd"'){|a,b| spaceship(a,b) >= 0 ? a.value : b.value }
mk_op("y yesNo ifElse", "b a a -> a", '0 2 3 y -> 3', promote: NotFirst, example2: '1 2 3 y -> 2'){|a,b,c| truthy($at, a.value) ? b.value : c.value }
mk_op("z zeroPad padDefault", "[a] -> [a]", '1,2,3 z -> [1,2,3,0,0,...', uses_zero: true){|a| padv(a, zero($rank,$at).const) }
mk_op("\\ setDiff", "[a] [a] -> [a]", '"abra" "ra"\\ -> "ba"'){|a,b| set_diff(a,b) }
mk_op("< lessThan", "a a -> int", '1,2,3 2< -> [1,0,0]'){|a,b| spaceship(a,b) == -1 ? 1 : 0 }
mk_op("& sortBy", "[a] [b] -> [a]", '"abc","d":len &, -> ["d","abc"]', promote: false){|a,b| sort_by(a,b) }
mk_op("| chunkWhen", "[a] [b] -> [[a]]", '"ab cd e":| -> ["ab ","cd ","e"]'){|a,b| chunk_while(a,b,$bt) }
mk_op("@ indices", "[a] a -> [int]", '"abcdc" \'c @ -> [2,4]'){|a,b| indices(a,b) }
mk_op("? isFirst", "[a] -> [int]", '"aardvark"? -> [1,0,1,1,1,0,0,1]', promote: false){|a| isuniq(a) }
mk_op("[ init", "[a] -> [a]", '"abc" [ -> "ab"', promote: false){|a| init(a) }

category('<a href="ops.html#slicingops">slicing ops</a> (char instead of int is substring, uppercase = any)')
mk_op("d drop", "[a] int -> [a]", '"abc" 2d -> "c"', example2: '"abc" "b" d -> "c"'){|a, b| drop(b.value, a) }
mk_op("g get", "[a] int -> a", '"abcd" 2g -> \'c\'', uses_zero: true, example2: '"abc" \'z g -> ""'){|a, b| z=zero($rank,$at); a = drop(b.value, a); a==[] || b.value < 0 ? z : a[0].value }
mk_op("k keep take", "{a} int -> [a]", '"abc" 2k -> "ab"', example2: '"abc" \'b k -> "a"'){|a, b| take(b.value, a) }
mk_op("# reshape", "[a] {int} -> [[a]]", '"abcdef" 2 # -> ["ab","cd","ef"]'){|a,b| reshape(a,b) }
mk_op("- cutAny", "[achar] {[[achar]]} -> [[achar]]",'"a-b.c" ".","-" - -> ["a","b","c"]', &cut_any=->a,b{cut_any(a,b) })
mk_op("+ scanAny", "[achar] {[[achar]]} -> [[achar]]", '"a-b.c" ".","-" + -> ["-","."]', &scan_any=->a,b{scan_any(a,b) })

category("folding ops") # (return type is type yielded)
mk_op("i iterate", "a -> [a]", '0 i 1,2,3+ -> [0,1,3,6]', block: true)
mk_op("e expand iterate0", "[a]", 'e 1,2,3+ -> [1,3,6]', block: true)
mk_op("f foldr", "a -> [a]", '0 f 1,2,3+ -> 6', block: true)
mk_op("m meld foldr0", "[a]", 'm 1,2,3+ -> 6', block: true)

category('<a href="syntax.html#otherwaystousevaluesmultipletimes">stack ops</a> (fns don\'t match >)')
mk_op(": dup", "a -> a", "5: -> 5 5", vectorize: false)
mk_op("; mdup", "a -> a", "5;1- -> 4 5", block: true, vectorize: false)
mk_op("] peek", "a b -> b", "5 4 ] -> 5 4 5", vectorize: false)
mk_op("! mpeek", "a b -> b", "5 4 ! 1+ -> 6 4 5", block: true, vectorize: false)

category("special symbols")
quickref_op("$", "arg", "5;$+ -> 10 5")
quickref_op(">", '<a href="syntax.html#codeclassinlinegtcodematching">end loop</a> or set <a href="syntax.html#implicitvalues">implicit</a>', "e))> -> [2,4,6...")
quickref_op(",", '<a href="vectorization.html">unvec</a> or data format or <a href="io.html#rawmodes">raw mode</a>', '"ab"j, -> ["ab"]')
quickref_op("'", 'char literal or <a href="io.html#rawmodes">raw line mode</a>', "'a -> 'a'")
quickref_op("=", "set next var", "5= A -> 5 5")

# possible overloads to consider (but complicated because of ab type or possibly useful promote
# G W
category('<a href="types.html#lowrankoverrides">low rank overloads</a><br> (subscript is number of times you may vectorize arg before needing `,`)')
mk_op("U uppercase", "char_2 -> char", "'a U -> 'A'"){|a| a=a.value; a>='a'.ord && a<='z'.ord ? a-32 : a }
mk_op("L lowercase", "char_1 -> char", "'A L -> 'a'"){|a| a=a.value; a>='A'.ord && a<='Z'.ord ? a+32 : a }
mk_op("p lines", "[char]_0 -> [[char]]", '"ab\nc\n" p -> ["ab","c"]'){|a| lines(a) }
mk_op("P charClass", "[char]_1 -> [char]", '"x" P -> "abc...z"'){|a| concat(char_class(a).const) }
mk_op("P digits", "int_2 -> [int]", '123 P -> [1,2,3]'){|a| v=to_base(a.value,10); v.empty? ? [0.const, Null] : reverse(v.const) }
mk_op("U undigits", "[int]_1 -> int", '1,2,3 U -> 123', promote: false){|a| undigits(a) }
mk_op("L lenFrom0 abs", "int_1 -> int", '5~L -> 5', example2: '5 L -> 5'){|a| a.value.abs }
mk_op("D digitsBase toBase", "int_1 int -> [int]", '6 2 D -> [1,1,0]'){|a,b|
  b.value == 0 ? [a, Null] : reverse(to_base(a.value,b.value).const) }
mk_op("X exBase fromBase", "[int] int_0 -> int", '1,1,0 2 X -> 6', promote: false){|a,b| from_base(a.value,b) }
mk_op("p bits (LSB)", "int_1 -> [int]", "6 p -> [0,1,1]"){|a| to_base(a.value, 2) }
mk_op("b rangeBegin rangeFrom", "x_0 -> [x]", "2b -> [2,3,4,..."){|a| range_from(a.value) }
mk_op("T rangeTo", "x_1 -> [x]", "3T -> [0,1,2]"){|a| range(zero(0,$at),a.value) }
mk_op("u unsquare sqrt", "int_1 -> int", "9u -> 3"){|a| square_root(a.value, 2) }
mk_op("H powOf10", "int_1 -> int", "2H -> 100"){|a| pow(10.const, a) }
mk_op("h powOf2", "int_0 -> int", "8h -> 256"){|a| pow(2.const, a) }
mk_op("B $, sortedSet", "[a]_0 -> [a]", "3,1,4,1 B -> [1,3,4]"){|a| to_lazy_list_rec(to_eager_list_rec(a).sort.uniq) }
# this overload also has doesn't allow coerce (handled specially in type infer)
mk_op("| {, range", "x_0 x_0 -> [x]", "8 5 | -> [5,6,7]"){|a,b| range(b.value, a.value) }
mk_op("V (, min", "a a_1 -> a", '4 5 V -> 4'){|a,b| spaceship(a,b) <= 0 ? a.value : b.value }

category("debugging ops")
mk_op("nil", "[a]", 'nil -> []'){ [] }
mk_op("pad", "[a] a -> [a]", '1,2 3 pad -> [1,2,3,3,3...'){|a, b| padv(a, b) }
mk_op("show", "a -> [char]", '1,2 show -> "[1,2]"', vectorize: false, &show=->a{ inspectv($at, $rank, a) })
mk_op("type", "a -> [char]", '1,2 type -> "[int]"', vectorize: false){|a| str_to_lazy_list(type_and_rank_to_str($at, $rank)) }
mk_op("version", "[char]", "version -> \"#{Version}, ruby#{RUBY_VERSION}, encoding=#{Encoding.default_external}\""){ str_to_lazy_list(Version + ", ruby#{RUBY_VERSION}, encoding=#{Encoding.default_external}") }
mk_op("del", "a -> ", '1 2 del -> 1', vectorize: false){|a| raise}
mk_op("let", "a -> ", "5 let a  a a+ -> 10", vectorize: false)
mk_op("set", "a -> a", "5 set a( a) -> 4 6", vectorize: false)
mk_op("input", "a", vectorize: false)


category("internal ops")
mk_op("implicit", "a", vectorize: false)
mk_op("$", "a", vectorize: false)
mk_op("superpad", "[a] -> [a]", promote: false){ raise }
mk_op("_id", "a -> a", vectorize: false){ raise }

# if change, be sure to update type2, example2 for main op for quickref
mk_op("^ charSubtraction", "char char -> int", "'b 'a ^ -> 1", &subt)
mk_op("+ add", "char int -> char", "'a 1+ -> 'b'", &add)

mk_op("/ rightJustify", "[aint] int -> [aint]", '1 3 /, -> [0,0,1]', uses_zero: true, &rjust)
mk_op("% split", "[aint] [aint] -> [[aint]]", '1,2,2,3 2 %, -> [[1],[3]]', &split)
mk_op("^ replicate", "[aint] int -> [aint]", '1,2 3^, -> [1,2,1,2,1,2]', &replicate)
mk_op("* join", "[[aint]] {[aint]} -> [aint]", '1,2,3j 4*, -> [1,4,2,4,3]', &join)
mk_op("_ words", "[aint] -> [[aint]]", '1,2,0,0,4 _, -> [[1,2],[4]]', &words)
mk_op("` strip", "[aint] -> [aint]", '1,2,0,0 `, -> [1,2]', &strip)

mk_op("- cutAny", "[aint] {[[aint]]} -> [[aint]]",'1,2,3,4,5 4,2j -, -> [[1],[3],[5]]', &cut_any)
mk_op("+ scanAny", "[aint] {[[aint]]} -> [[aint]]", '1,2,3,4,5 4,2j +, -> [[2],[4]]', &scan_any)

###### BEGIN if update, update table in slicing ops #####################

mk_op("# cut splitKeepEmpties", "[achar] {[achar]} -> [[achar]]",'"a..b.c." "."# -> ["a","","b","c",""]'){|a, b| cut(a,b) }
mk_op("d dropUntilAfterSubstring", "[achar] [achar] -> [achar]", '"beatles" "at" d -> "les"'){|a, b|
  drop(substr_ind(a, b) + len(b.value), a) }
mk_op("k keepUntilSubstring", "[achar] [achar] -> [achar]", '"beatles" "at" k -> "be"'){|a, b|
  take(substr_ind(a, b), a) }
mk_op("g getSubstring", "[achar] [achar] -> [achar]", '"beatles" "at","z" g -> ["at",""]'){|a, b|
  take(len(b.value), promise{ drop(substr_ind(a, b), a) }) }
mk_op("D dropUntilAfterAnySubstring", "[achar] [[achar]] -> [achar]", '"beatles" "z","at" D -> "les"'){|a, b|
  match_pos, match_val = *any_substr_ind(a, b)
  drop(match_pos + len(match_val.value), a) }
mk_op("K keepUntilAnySubstring", "[achar] [[achar]] -> [achar]", '"beatles" "z","at" K -> "be"'){|a, b|
  match_pos, match_val = *any_substr_ind(a, b)
  take(match_pos, a) }
mk_op("G getAnySubstring", "[achar] [[achar]] -> [achar]", '"beatles" "z","at" G -> "at"'){|a, b|
  match_pos, match_val = *any_substr_ind(a, b)
  take(len(match_val.value), promise{ drop(match_pos, a) }) }

###### END if update, update table in slicing ops #####################

category("intuitive special promotes (don't need to show them in quickref)")
mk_op("* join joinM", "[achar]_0 {[achar]}-> [achar]", '"123" \'a * -> "1a2a3"', promote: NotFirst){|a, b| join(map(a){|i| [i,Null] }.const,b)}
mk_op("* join joinM", "[aint]_0 {[aint]}-> [aint]", '1,2,3 4 *, -> [1,4,2,4,3]', promote: NotFirst){|a, b| join(map(a){|i| [i,Null] }.const,b)}

Unlines = Way.new("unlines", [2], [1], [CharType], CharType, 0, nil, nil, [false], [nil], ->a{
  concat(map(a){|e| append(e, [10.const, Null].const) }.const)
})
Unwords = Way.new("unwords", [2], [1], [CharType], CharType, 0, nil, nil, [false], [nil], ->a{
  join(a, repeat([32.const, Null].const).const)
})
