require "spec"
require "uri/params"

class URI
  describe Params do
    describe ".new" do
      it { Params.new.should eq(Params.parse("")) }
    end

    describe ".parse" do
      {
        {"", {} of String => Array(String)},
        {"&&", {} of String => Array(String)},
        {"   ", {"   " => [""]}},
        {"foo=bar", {"foo" => ["bar"]}},
        {"foo=bar&foo=baz", {"foo" => ["bar", "baz"]}},
        {"foo=bar&baz=qux", {"foo" => ["bar"], "baz" => ["qux"]}},
        {"foo=bar;baz=qux", {"foo" => ["bar"], "baz" => ["qux"]}},
        {"foo=hello%2Bworld", {"foo" => ["hello+world"]}},
        {"foo=hello+world", {"foo" => ["hello world"]}},
        {"foo=", {"foo" => [""]}},
        {"foo", {"foo" => [""]}},
        {"foo=&bar", {"foo" => [""], "bar" => [""]}},
        {"bar&foo", {"bar" => [""], "foo" => [""]}},
        {"foo=bar=qux", {"foo" => ["bar=qux"]}},
      }.each do |(from, to)|
        it "parses #{from}" do
          Params.parse(from).should eq(Params.new(to))
        end
      end
    end

    describe ".build" do
      {
        {"foo=bar", {"foo" => ["bar"]}},
        {"foo=bar&foo=baz", {"foo" => ["bar", "baz"]}},
        {"foo=bar&baz=qux", {"foo" => ["bar"], "baz" => ["qux"]}},
        {"foo=hello%2Bworld", {"foo" => ["hello+world"]}},
        {"foo=hello+world", {"foo" => ["hello world"]}},
        {"foo=", {"foo" => [""]}},
        {"foo=&bar=", {"foo" => [""], "bar" => [""]}},
        {"bar=&foo=", {"bar" => [""], "foo" => [""]}},
      }.each do |(to, from)|
        it "builds form from #{from}" do
          encoded = Params.build do |form|
            from.each do |key, values|
              values.each do |value|
                form.add(key, value)
              end
            end
          end

          encoded.should eq(to)
        end
      end

      it "turns spaces to %20 if wanted" do
        encoded = Params.build(space_to_plus: false) do |form|
          form.add("foo bar", "hello world")
        end

        encoded.should eq("foo%20bar=hello%20world")
      end

      it "builds with IO" do
        io = IO::Memory.new
        Params.build(io) do |form|
          form.add("custom", "key")
        end
        io.to_s.should eq("custom=key")
      end
    end

    describe ".encode" do
      it "builds from hash" do
        encoded = Params.encode({"foo" => "bar", "baz" => ["quux", "quuz"]})
        encoded.should eq("foo=bar&baz=quux&baz=quuz")
      end

      it "builds from hash with IO" do
        io = IO::Memory.new
        Params.encode(io, {"foo" => "bar", "baz" => ["quux", "quuz"]})
        io.to_s.should eq("foo=bar&baz=quux&baz=quuz")
      end

      it "builds from named tuple" do
        encoded = Params.encode({foo: "bar", baz: ["quux", "quuz"]})
        encoded.should eq("foo=bar&baz=quux&baz=quuz")
      end

      it "builds from named tuple with IO" do
        io = IO::Memory.new
        Params.encode(io, {foo: "bar", baz: ["quux", "quuz"]})
        io.to_s.should eq("foo=bar&baz=quux&baz=quuz")
      end
    end

    describe "#to_s" do
      it "serializes params to http form" do
        params = Params.parse("foo=bar&foo=baz&baz=qux")
        params.to_s.should eq("foo=bar&foo=baz&baz=qux")
      end

      it "turns spaces to + by default" do
        params = Params.parse("foo+bar=hello+world")
        params.to_s.should eq("foo+bar=hello+world")
      end

      it "turns spaces to %20 if space_to_plus is false" do
        params = Params.parse("foo+bar=hello+world")
        params.to_s(space_to_plus: false).should eq("foo%20bar=hello%20world")
      end
    end

    it "#inspect" do
      URI::Params.new.inspect.should eq "URI::Params{}"

      URI::Params{"foo" => ["bar", "baz"], "baz" => ["qux"]}.inspect.should eq %(URI::Params{"foo" => ["bar", "baz"], "baz" => ["qux"]})
    end

    describe "#[](name)" do
      it "returns first value for provided param name" do
        params = Params.parse("foo=bar&foo=baz&baz=qux")
        params["foo"].should eq("bar")
        params["baz"].should eq("qux")
      end

      it "raises KeyError when there is no such param" do
        params = Params.parse("foo=bar&foo=baz&baz=qux")
        expect_raises KeyError do
          params["non_existent_param"]
        end
      end
    end

    describe "#[]?(name)" do
      it "returns first value for provided param name" do
        params = Params.parse("foo=bar&foo=baz&baz=qux")
        params["foo"]?.should eq("bar")
        params["baz"]?.should eq("qux")
      end

      it "return nil when there is no such param" do
        params = Params.parse("foo=bar&foo=baz&baz=qux")
        params["non_existent_param"]?.should be_nil
      end
    end

    describe "#has_key?(name)" do
      it "returns true if param with provided name exists" do
        params = Params.parse("foo=bar&foo=baz&baz=qux")
        params.has_key?("foo").should be_true
        params.has_key?("baz").should be_true
      end

      it "return false if param with provided name does not exist" do
        params = Params.parse("foo=bar&foo=baz&baz=qux")
        params.has_key?("non_existent_param").should be_false
      end
    end

    describe "#[]=(name, value)" do
      it "sets value for provided param name" do
        params = Params.parse("foo=bar&foo=baz&baz=qux")
        params["foo"] = "notfoo"
        params.fetch_all("foo").should eq(["notfoo"])
      end

      it "adds new name => value pair if there is no such param" do
        params = Params.parse("foo=bar&foo=baz&baz=qux")
        params["non_existent_param"] = "test"
        params.fetch_all("non_existent_param").should eq(["test"])
      end

      it "sets value for provided param name (array)" do
        params = Params.parse("foo=bar&foo=baz&baz=qux")
        params["non_existent_param"] = ["test", "something"]
        params.fetch_all("non_existent_param").should eq(["test", "something"])
      end
    end

    describe "#fetch(name, default)" do
      it "returns first value for provided param name" do
        params = Params.parse("foo=bar&foo=baz&baz=qux")
        params.fetch("foo", "aDefault").should eq("bar")
        params.fetch("baz", "aDefault").should eq("qux")
      end

      it "return default value when there is no such param" do
        params = Params.parse("foo=bar&foo=baz&baz=qux")
        params.fetch("non_existent_param", "aDefault").should eq("aDefault")
      end
    end

    describe "#fetch(name, &block)" do
      it "returns first value for provided param name" do
        params = Params.parse("foo=bar&foo=baz&baz=qux")
        params.fetch("foo") { "fromBlock" }.should eq("bar")
        params.fetch("baz") { "fromBlock" }.should eq("qux")
      end

      it "return default value when there is no such param" do
        params = Params.parse("foo=bar&foo=baz&baz=qux")
        params.fetch("non_existent_param") { "fromBlock" }.should eq("fromBlock")
      end
    end

    describe "#fetch_all(name)" do
      it "fetches list of all values for provided param name" do
        params = Params.parse("foo=bar&foo=baz&baz=qux&iamempty")
        params.fetch_all("foo").should eq(["bar", "baz"])
        params.fetch_all("baz").should eq(["qux"])
        params.fetch_all("iamempty").should eq([""])
        params.fetch_all("non_existent_param").should eq([] of String)
      end
    end

    describe "#add(name, value)" do
      it "appends new value for provided param name" do
        params = Params.parse("foo=bar&foo=baz&baz=qux&iamempty")

        params.add("foo", "zeit")
        params.fetch_all("foo").should eq(["bar", "baz", "zeit"])

        params.add("baz", "exit")
        params.fetch_all("baz").should eq(["qux", "exit"])

        params.add("iamempty", "not_empty_anymore")
        params.fetch_all("iamempty").should eq(["not_empty_anymore"])

        params.add("non_existent_param", "something")
        params.fetch_all("non_existent_param").should eq(["something"])
      end
    end

    describe "#set_all(name, values)" do
      it "sets values for provided param name" do
        params = Params.parse("foo=bar&foo=baz&baz=qux")

        params.set_all("baz", ["hello", "world"])
        params.fetch_all("baz").should eq(["hello", "world"])

        params.set_all("foo", ["something"])
        params.fetch_all("foo").should eq(["something"])

        params.set_all("non_existent_param", ["something", "else"])
        params.fetch_all("non_existent_param").should eq(["something", "else"])
      end
    end

    describe "#dup" do
      it "gives a whole new set of params" do
        ary = ["bar"]
        params = Params{"foo" => ary}
        duped = params.dup

        ary << "baz"

        duped.fetch_all("foo").should eq ["bar"]
        params.fetch_all("foo").should eq ["bar", "baz"]
      end
    end

    describe "#clone" do
      it "gives a whole new set of params" do
        ary = ["bar"]
        params = Params{"foo" => ary}
        duped = params.clone

        ary << "baz"

        duped.fetch_all("foo").should eq ["bar"]
        params.fetch_all("foo").should eq ["bar", "baz"]
      end
    end

    describe "#each" do
      it "calls provided proc for each name, value pair, including multiple values per one param name" do
        received = [] of {String, String}

        params = Params.parse("foo=bar&foo=baz&baz=qux")
        params.each do |name, value|
          received << {name, value}
        end

        received.should eq([
          {"foo", "bar"},
          {"foo", "baz"},
          {"baz", "qux"},
        ])
      end
    end

    describe "#delete" do
      it "deletes first value for provided param name and returns it" do
        params = Params.parse("foo=bar&foo=baz&baz=qux")

        params.delete("foo").should eq("bar")
        params.fetch_all("foo").should eq(["baz"])

        params.delete("baz").should eq("qux")
        expect_raises KeyError do
          params["baz"]
        end
      end
    end

    describe "#delete_all" do
      it "deletes all values for provided param name and returns them" do
        params = Params.parse("foo=bar&foo=baz&baz=qux")

        params.delete_all("foo").should eq(["bar", "baz"])
        expect_raises KeyError do
          params["foo"]
        end
      end
    end

    describe "#merge!" do
      it "modifies the receiver" do
        params = Params.parse("foo=bar&foo=baz&qux=zoo")
        other_params = Params.parse("foo=buzz&foo=extra")

        params.merge!(other_params, replace: false)

        params.to_s.should eq("foo=bar&foo=baz&foo=buzz&foo=extra&qux=zoo")
      end

      describe "does not modify the other params" do
        it "with replace: true" do
          params = Params.parse("foo=bar")
          other_params = Params.parse("foo=buzz&foo=extra")

          params.merge!(other_params, replace: true)
          params.add("foo", "another")

          other_params.to_s.should eq("foo=buzz&foo=extra")
        end

        it "with replace: false" do
          params = Params.parse("foo=bar")
          other_params = Params.parse("foo=buzz&foo=extra")

          params.merge!(other_params, replace: false)
          params.add("foo", "another")

          other_params.to_s.should eq("foo=buzz&foo=extra")
        end
      end
    end

    describe "#merge" do
      it "replaces all values with the same key by default" do
        params = Params.parse("foo=bar&foo=baz&qux=zoo")
        other_params = Params.parse("foo=buzz&foo=extra")

        params.merge(other_params).to_s.should eq("foo=buzz&foo=extra&qux=zoo")
      end

      it "appends values with the same key with replace: false" do
        params = Params.parse("foo=bar&foo=baz&qux=zoo")
        other_params = Params.parse("foo=buzz&foo=extra")

        params.merge(other_params, replace: false).to_s.should eq("foo=bar&foo=baz&foo=buzz&foo=extra&qux=zoo")
      end

      it "does not modify the receiver" do
        params = Params.parse("foo=bar&foo=baz&qux=zoo")
        other_params = Params.parse("foo=buzz&foo=extra")

        params.merge(other_params)

        params.to_s.should eq("foo=bar&foo=baz&qux=zoo")
      end
    end

    describe "#empty?" do
      it "test empty?" do
        Params.parse("foo=bar&foo=baz&baz=qux").empty?.should be_false
        Params.parse("").empty?.should be_true
        Params.new.empty?.should be_true
      end
    end

    describe "#==" do
      it "compares other" do
        a = Params.parse("a=foo&b=bar")
        b = Params.parse("a=bar&b=foo")
        (a == a).should be_true
        (b == b).should be_true
        (a == b).should be_false
      end

      it "compares other types" do
        a = Params.parse("a=foo&b=bar")
        b = "other type"
        (a == b).should be_false
      end
    end
  end
end
