"use strict"

Signature = require "../src/signature"
after = Signature.after

require('chai').should()

class Test
class SubTest extends Test

describe "signature", ->

  it "should detect empty", ->
    after().run(-> "empty").get().should.equal "empty"

  it "should understand after().run(fn).get() syntax", ->
    after(String).run((x)-> x).get("foo").should.equal "foo"

  it "should understand Signature.with().run(fn) syntax", ->
    xx = Signature.with("foo")
      .after(String).run((x)-> x)
      .after(nullable: String).run(-> "bar")
      .after(undefined).run(-> "bar")
    xx.get().should.equal "foo"

  it "should understand after().run(fn).getWithContext() syntax", ->
    class X
      ctorSig = after(String).run((x)-> @xx = x)
      constructor: -> ctorSig.getWithContext this, arguments...
    new X("foo").xx.should.equal "foo"

  it "should understand after().run(fn).asMethod() syntax", ->
    class X
      constructor: after(Object).run((x)-> @xx = x; undefined).
        after(String).run((x)-> @xx = x).asMethod()
    new X({ foo: "bar" }).xx.foo.should.equal "bar"
    new X("bar").xx.should.equal "bar"

  it "should throw if any arguments and no arguments assumed", ->
    sig = after().run -> "empty"
    (-> sig.get ['']).should.throw()

  it "should throw meaningful message and stack trace", ->
    sig = after(String, Number).run(->)
      .after(String, nullable: Number).run(->)
      .after(String, Array).run ->
    class X
      constructor: -> sig.get arguments...
    try
      new X('', 'yyy')
    catch err
      err.stack.should.contain "new X('', 'yyy')"
      err.stack.should.not.contain "Signature"
      i = err.stack.indexOf "\n  at"
      err.stack.substring(i+1).split('\n')[0].should.contain "new X"

  it "should throw when no match found", ->
    sig = after(Number).run ->
    (-> sig.get()).should.throw("has no matching signature")

  it "should allow alternatives", ->
    sig = after([undefined, String], String).run -> "success"
    sig.get(null, 'xxxx').should.equal "success"
    sig.get('', '').should.equal "success"
    (-> sig.get '', 1).should.throw()

  it "should allow alternatives 2", ->
    sig = after([Number, String], String).run -> "success"
    sig.get(1, 'xxxx').should.equal "success"
    sig.get('', '').should.equal "success"
    (-> sig.get '', 1).should.throw()

  it "should understand modifiers", ->
    sig = after(nullable: String, String).run -> "success"
    sig.get(null, 'xxxx').should.equal "success"
    sig.get('xxxx', '').should.equal "success"
    (-> sig.get '', 1).should.throw()

  it "should detect custom classes", ->
    sig = after(Test, String).run -> "success"
    sig.get(new Test(), 'x').should.equal "success"
    sig.get(new SubTest(), 'x').should.equal "success"
    (-> sig.get {}, 'x').should.throw()
    sig = after(SubTest, String).run -> "success"
    (-> sig.get new Test(), 'x').should.throw("no matching signature")

  it "should detect Function class", ->
    sig = after(Function).run -> "success"
    sig.get(->).should.equal "success"

  it "should detect Arrays", ->
    sig = after(Array, Array).run -> "success"
    sig.get([],[]).should.equal "success"

  it "should detect boolean", ->
    sig = after(Array, Boolean).run -> "success"
    sig.get([],true).should.equal "success"
    sig.get([],false).should.equal "success"

  it "should detect rest", ->
    sig = after(String, rest: String).run (args...)->
      "success" + args.join('')
    sig.get('1','2','3','4').should.equal "success12,3,4"
    (-> sig.get '1','2','3',4).should.throw("has no matching signature")

  it "should detect rest in the middle", ->
    sig = after(Array, rest: String, Number, Number).run (args...)->
      "success" + args.join('')
    sig.get([1], '2','3',4, 5).should.equal "success12,345"
    (-> sig.get [1], '2','3',4).should.throw("no matching signature")

  it "should allow other modifiers inside rest-modifier", ->
    sig = after(Array, rest: nullable: String, Number, Number).run (args...)->
      "success" + args.join('')
    sig.get([1], '2', null,4, 5).should.equal "success12,45"
    (-> sig.get [[1], null,'3',4]).should.throw("no matching signature")
    sig = after(rest: undefined).run (args)-> args.join('')
    sig.get(123, '45').should.equal "12345"
    sig.get("asdf", 8, "qwer").should.equal "asdf8qwer"

  it "should treat undefined as any type", ->
    sig = after(undefined).run (a)-> a
    sig.get("asdf").should.equal "asdf"
    sig.get(12345).should.equal 12345
    a = new Date()
    sig.get(a).should.equal a
    (-> sig.get '3',4).should.throw("no matching signature")

  it "apply this", ->
    class Test
      x = after(String).run (a)-> @foo = a
      constructor: -> x.getWithContext(this, arguments...)
    test = new Test "bar"
    test.foo.should.equal "bar"

  it "asdf", ->
    sig = new Signature(null).after String, (args...)-> args
    # fn = -> sig.get(arguments...)[0]
    # fn("hello").should.equal "hello"
    fn = ->
      Signature.with(arguments...)
        .after(String).run((args...)-> args).get()
    fn("hello")[0].should.equal "hello"

  it "qwer", ->
    sig = after(Array, Array).run (args...)-> args
    fn = -> sig.get(arguments...)
    fn([1,2],[1,3]).length.should.equal 2
