require_relative "../spec_helper"
require 'date'

describe "class_matchers plugin" do 
  it "allows class specific regexps with type conversion for class matchers" do
    app(:bare) do
      plugin :class_matchers
      class_matcher(Date, /(\d\d\d\d)-(\d\d)-(\d\d)/){|y,m,d| Date.new(y.to_i, m.to_i, d.to_i) if Date.valid_date?(y.to_i, m.to_i, d.to_i)}
      class_matcher(Array, /(\w+)\/(\w+)/){|a, b| [[a, 1], [b, 2]]}
      class_matcher(Hash, /(\d+)\/(\d+)/){|a, b| [{a.to_i=>b.to_i}]}

      klass = Class.new
      def klass.to_s; "klass" end
      class_matcher(klass, Integer){|i| i*2 unless i == 10}

      plugin :symbol_matchers
      symbol_matcher(:i, /i(\d+)/, &:to_i)
      klass2 = Class.new
      def klass2.to_s; "klass2" end
      class_matcher(klass2, :i){|i| i*3}

      symbol_matcher(:j, /j(\d+)/)
      klass3 = Class.new
      def klass3.to_s; "klass3" end
      class_matcher(klass3, :j){|j| j*3}

      klass4 = Class.new
      def klass4.to_s; "klass4" end
      class_matcher(klass4, String){|i| i*2}

      klass5 = Class.new
      def klass5.to_s; "klass5" end
      class_matcher(klass5, klass){|i| i*3}

      klass6 = Class.new
      def klass6.to_s; "klass6" end
      class_matcher(klass6, klass)

      klass7 = Class.new
      def klass7.to_s; "klass7" end
      class_matcher(klass7, String)

      klass8 = Class.new
      def klass8.to_s; "klass8" end
      class_matcher(klass8, :d){|i| i*2}

      klass9 = Class.new
      def klass9.to_s; "klass9" end
      class_matcher(klass9, :d)

      route do |r|
        r.on Array do |(a,b), (c,d)|
          r.get 'X', klass5 do |i|
            [a, b, c, d, i].join('-')
          end
          r.get 'Y', [klass6, klass7] do |i|
            [a, b, c, d, i].join('-')
          end
          r.get 'Z1', klass8 do |i|
            [a, b, c, d, i].join('-')
          end
          r.get 'Z2', klass9 do |i|
            [a, b, c, d, i].join('-')
          end
          r.get Date do |date|
            [date.year, date.month, date.day, a, b, c, d].join('-')
          end
          r.get Hash do |h|
            [h.to_a.inspect, a, b, c, d].join('-')
          end
          r.get Array do |(a1,b1), (c1,d1)|
            [a1, b1, c1, d1, a, b, c, d].join('-')
          end
          r.get klass do |i|
            [a, b, c, d, i].join('-') + '-1'
          end
          r.get klass2 do |i|
            [a, b, c, d, i].join('-') + '-2'
          end
          r.get klass3 do |i|
            [a, b, c, d, i].join('-') + '-3'
          end
          r.get klass4 do |i|
            [a, b, c, d, i].join('-') + '-4'
          end
          r.is do
            [a, b, c, d].join('-')
          end
          "array"
        end
        ""
      end
    end

    body("/c").must_equal ''
    body("/c/d").must_equal 'c-1-d-2'
    body("/c/d/e/f/g").must_equal 'array'
    body("/c/d/2009-10-a").must_equal 'c-1-d-2-2009-10-a2009-10-a-4'
    body("/c/d/2009-10-01").must_equal '2009-10-1-c-1-d-2'
    body("/c/d/2009-13-01").must_equal "c-1-d-2-2009-13-012009-13-01-4"
    body("/c/d/1/2").must_equal '[[1, 2]]-c-1-d-2'
    body("/c/d/e/f").must_equal 'e-1-f-2-c-1-d-2'
    body("/c/d/3").must_equal 'c-1-d-2-6-1'
    body("/c/d/10").must_equal 'c-1-d-2-1010-4'
    body("/c/d/i3").must_equal 'c-1-d-2-9-2'
    body("/c/d/j3").must_equal 'c-1-d-2-333-3'
    body("/c/d/i").must_equal 'c-1-d-2-ii-4'
    body("/c/d/X/3").must_equal 'c-1-d-2-18'
    body("/c/d/X/10").must_equal 'X-1-10-2-c-1-d-2'
    body("/c/d/Y/3").must_equal 'c-1-d-2-6'
    body("/c/d/Y/a").must_equal 'c-1-d-2-a'
    body("/c/d/Z1/3").must_equal 'c-1-d-2-33'
    body("/c/d/Z2/3").must_equal 'c-1-d-2-3'
  end

  it "raises errors for unsupported calls to class matcher" do
    app(:class_matchers){}
    c = Class.new
    proc{app.class_matcher(:foo, /a/)}.must_raise Roda::RodaError
    proc{app.class_matcher(c, Hash)}.must_raise Roda::RodaError
    proc{app.class_matcher(c, :foo)}.must_raise Roda::RodaError
    app.plugin :symbol_matchers
    proc{app.class_matcher(c, :foo)}.must_raise Roda::RodaError
    proc{app.class_matcher(c, Object.new)}.must_raise Roda::RodaError
  end

  it "respects Integer_matcher_max plugin when using class_matcher with Integer matcher" do
    c = Class.new
    app(:class_matchers){|r| r.is(c){|x| (x*3).to_s}}
    app.class_matcher(c, Integer)
    body("/4").must_equal "12"
    body("/1000000000000000000000").must_equal "3000000000000000000000"
    app.plugin :Integer_matcher_max
    body("/1000000000000000000000").must_equal ""
    app.plugin :Integer_matcher_max, 1000000000000000000000
    body("/1000000000000000000000").must_equal "3000000000000000000000"
    body("/1000000000000000000001").must_equal ""
  end

  it "respects Integer_matcher_max plugin when loaded first" do
    c = Class.new
    app(:bare) do
      plugin :Integer_matcher_max
      plugin :class_matchers
      route{|r| r.is(c){|x| (x*3).to_s}}
    end
    app.class_matcher(c, Integer)
    body("/4").must_equal "12"
    body("/1000000000000000000000").must_equal ""
    app.plugin :Integer_matcher_max, 1000000000000000000000
    body("/1000000000000000000000").must_equal "3000000000000000000000"
    body("/1000000000000000000001").must_equal ""
  end

  it "handles conversion block returning falsey for matcher based on String " do
    app(:class_matchers) do |r|
      r.is(Array){|s| s}
      r.remaining_path
    end
    app.class_matcher(Array, String){|s| s*2 unless s == 'a'}
    body('/a').must_equal '/a'
    body('/b').must_equal 'bb'
  end

  it "yields multiple arguments in matcher based on String " do
    app(:class_matchers) do |r|
      r.is(Array){|s, s2| "#{s}-#{s2}"}
    end
    app.class_matcher(Array, String){|s| [s, s*2]}
    body('/a').must_equal 'a-aa'
  end

  it "yields hash instances as single arguments" do
    app(:class_matchers) do |r|
      r.is('a', Array){|h| h.to_a.join(',')}
      r.is('h', Hash){|h| h.to_a.join('-')}
    end
    app.class_matcher(Hash, /(\w)(\w)/){|k,v| {k=>v}}
    app.class_matcher(Array, Hash){|h| h['c'] = 'd'; h }
    body('/h/ab').must_equal 'a-b'
    body('/a/ab').must_equal 'a,b,c,d'
  end

  it "freezes :class_matchers option when freezing app" do
    app(:class_matchers){|r| }
    app.freeze
    app.opts[:class_matchers].frozen?.must_equal true
  end
end
