# encoding: UTF-8

require File.expand_path('../../test_helper', __FILE__)

describe "Alias" do

  start do
    site = setup_site
    template_root = File.expand_path(File.join(File.dirname(__FILE__), "../fixtures/templates/aliases"))
    let(:template_root) { template_root }
    site.paths.add(:templates, template_root)
    renderer = S::Output::Template::Renderer.new(site, false)
    let(:renderer) { renderer }
    let(:site) { site }

    Content.delete

    Page.field :title
    Page.box :box1
    Page.box :box2

    class ::A < ::Piece
      field :a_field1
      field :a_field2
      field :image, :image

      style :a_style
      def alias_title
        a_field1.value
      end
    end

    class ::AA < ::A
      field :aa_field1
      style :aa_style
    end

    class ::AAA < ::AA
      field :aaa_field1
    end

    class ::B < ::Page
      field :b_field1
      layout :b
    end

    class ::BB < ::B
      field :bb_field1

      box :box1
    end

    class ::AAlias < ::Piece
      alias_of :A

      field :a_alias_field1
      style :a_alias_style
    end

    class ::AAAlias < ::Piece
      alias_of :AA
    end

    class ::AAAAlias < ::Piece
      alias_of :AAA
    end


    class ::BAlias < ::Page
      alias_of :B
      box :box1
    end

    class ::BBAlias < ::Piece
      alias_of :BB
    end

    class ::MultipleAlias < ::Piece
      alias_of :AA, :B
    end

    class ::ProcAlias < ::Piece
      alias_of proc { Content.root.children }
    end

    root = ::Page.create
    aliases = ::Page.create(:slug => "aliases").reload
    root.box1 << aliases
    a = A.create(:a_field1 => "@a.a_field1").reload
    aa = AA.create.reload
    aaa1 = AAA.create(:aaa_field1 => "aaa1").reload
    aaa2 = AAA.create.reload
    b = B.new(:slug => "b")
    root.box1 << b
    bb = BB.new(:slug => "bb", :bb_field1 => "BB")
    root.box1 << bb
    root.save.reload

    let(:root) { root }
    let(:aliases) { aliases }
    let(:a) { a }
    let(:aa) { aa }
    let(:aaa1) { aaa1 }
    let(:aaa2) { aaa2 }
    let(:b) { b }
    let(:bb) { bb }
  end

  finish do
    [:A, :AA, :AAA, :B, :BB, :AAlias, :AAAlias, :AAAAlias, :BBAlias, :BAlias, :MultipleAlias, :ProcAlias].each do |c|
      Object.send(:remove_const, c) rescue nil
    end
    Content.delete
    teardown_site
  end

  def assert_same_content(c1, c2)
    assert_equal c2.length, c1.length
    c1 = c1.dup.sort { |a, b| a.id <=> b.id }
    c2 = c2.dup.sort { |a, b| a.id <=> b.id }
    c1.each_with_index do |a, i|
      b = c2[i]
      assert_equal b.id, a.id
      assert_equal b.class, a.class
    end
  end

  before do
    @site = site
  end

  after do
  end

  describe "All alias" do
    describe "class methods" do
      it "provide a list of available instances that includes all subclasses" do
        assert_same_content AAlias.targets, [a, aa, aaa1, aaa2]
        assert_same_content AAAlias.targets, [aa, aaa1, aaa2]
        assert_same_content AAAAlias.targets, [aaa1, aaa2]
      end

      it "use the first available string field as the alias title" do
        Piece.field :something
        target = Piece.new(:something => "something")
        target.alias_title.must_equal "something"
      end

      it "allow aliasing multiple classes" do
        assert_same_content MultipleAlias.targets, [aa, aaa1, aaa2, b, bb]
      end

      it "be creatable with a target" do
        instance = AAlias.create(:target => a).reload
        instance.target.must_equal a
        a.reload.aliases.must_equal [instance]
      end

      it "have a back link in the target" do
        instance1 = AAlias.create(:target => a).reload
        instance2 = AAlias.create(:target => a).reload
        assert_same_content a.reload.aliases, [instance1, instance2]
      end

      it "accept a proc that returns an array as a target list generator" do
        assert_same_content ProcAlias.targets, root.children
      end

      describe "with container options" do
        before do
          @page = ::Page.new(:uid => "thepage")
          4.times { |n|
            @page.box1 << A.new
            @page.box1 << AA.new
            @page.box2 << A.new
            @page.box2 << AA.new
          }
          @page = @page.save.reload
        end

        after do
          Object.send(:remove_const, 'X') rescue nil
          Object.send(:remove_const, 'XX') rescue nil
          Object.send(:remove_const, 'XXX') rescue nil
        end

        it "allow for selecting only content from within one box" do
          container_proc = Proc.new { site['$thepage'].box1 }
          ::X = Class.new(::Piece) do
            alias_of :A, :container => container_proc
          end
          ::XX = Class.new(::Piece) do
            alias_of :AA, :container => container_proc
          end
          targets = lambda { |a, target|
            [(a.targets), @page.box1.select { |p| target === p }].map { |a| Set.new(a) }
          }
          expected, actual = targets.call(X, A)
          actual.must_equal expected
          expected, actual = targets.call(XX, AA)
          actual.must_equal expected
        end

        it "allow for selecting only content from a range of boxes" do
          container_proc = Proc.new { [site['$thepage'].box1, site['$thepage'].box2] }
          ::X = Class.new(::Piece) do
            alias_of :A, :container => container_proc
          end
          ::XX = Class.new(::Piece) do
            alias_of :AA, :container => container_proc
          end
          assert_same_content X.targets, @page.box1.select { |p| A === p } + @page.box2.select { |p| A === p }
          assert_same_content XX.targets, @page.box1.select { |p| AA === p } + @page.box2.select { |p| AA === p }
        end

        it "allow for selecting only content from within one page" do
          container_proc = Proc.new { site['$thepage'] }
          ::X = Class.new(::Piece) do
            alias_of :A, :container => container_proc
          end
          ::XX = Class.new(::Piece) do
            alias_of :AA, :container => container_proc
          end
          assert_same_content X.targets, @page.content.select { |p| A === p }
          assert_same_content XX.targets, @page.content.select { |p| AA === p }
        end

        it "allow for selecting only content from a range of pages & boxes" do
          page2 = ::Page.new(:uid => "thepage2")
          4.times { |n|
            page2.box1 << A.new
            page2.box1 << AA.new
            page2.box2 << A.new
            page2.box2 << AA.new
          }
          page2.save.reload
          container_proc = Proc.new { [site['$thepage'].box1, site['$thepage2']] }
          ::X = Class.new(::Piece) do
            alias_of :A, :AA, :container => container_proc
          end
          container_proc = Proc.new { [site['$thepage'], site['$thepage2'].box2] }
          ::XX = Class.new(::Piece) do
            alias_of :AA, :container => container_proc
          end
          assert_same_content X.targets(@page, @page.box1), @page.box1.contents + page2.content
          assert_same_content XX.targets, @page.content.select { |p| AA === p } + page2.box2.select { |p| AA === p }
        end

        it "allow for selecting content only from the content of the owner of the box" do
          class ::X < ::Piece
            alias_of proc { |owner| owner.box1.contents }
          end
          class ::XX < ::Piece
            alias_of proc { |owner, box| box.contents }
          end
          class ::XXX < ::Piece
            alias_of :A, :container => proc { |owner, box| box }
          end
          assert_same_content X.targets(@page), @page.box1.contents
          assert_same_content XX.targets(@page, @page.box1), @page.box1.contents
          assert_same_content XX.targets(@page, @page.box2), @page.box2.contents
          assert_same_content XXX.targets(@page, @page.box1), @page.box1.contents.select { |p| A === p }
        end

        it "allow for filtering instances according to some arbitrary proc" do
          pieces = [@page.box1.entries.first, @page.box2.entries.first]
          _filter = lambda { |c|
            pieces.map(&:id).include?(c.id)
          }
          ::X  = Class.new(::Piece) do
            alias_of :A, :filter => _filter
          end
          assert_same_content pieces, X.targets
        end

        it "allow for filtering instances according to current page content" do
          @page.box1 << AAA.create
          @page.box2 << AAA.create
          @page.save.reload
          allowable = AAA.all - @page.box1.contents
          ::X  = Class.new(::Piece) do
            alias_of :AAA, :filter => proc { |choice, page, box| !box.include?(choice) }
          end
          assert_same_content allowable, X.targets(@page, @page.box1)
        end

        it "allow for ensuring the uniqueness of the entries" do
          aaa = AAA.all
          ::X  = Class.new(::Piece) do
            alias_of :AAA, :unique => true
          end
          @page.box1 << aaa.first
          @page.save.reload
          assert_same_content [aaa.last], X.targets(@page, @page.box1)
        end

        it "allow for returning an arbitrary list of results generated by a proc" do
          results = [mock, mock, mock]
          ::X  = Class.new(::Piece) do
            alias_of proc { results }
          end
          ::X.targets.must_equal  results
        end

      end
    end

    describe "instances" do
      before do
        @a_alias = AAlias.create(:target => a).reload
        @aa_alias = AAAlias.create(:target => aa).reload
        @aaa_alias = AAAAlias.create(:target => aaa1).reload
      end

      it "have their own fields" do
        assert @a_alias.field?(:a_alias_field1)
      end

      it "provide access to their target" do
        @a_alias.target.must_equal a
      end


      # TODO
      it "reference the aliases fields before the targets"

      it "present their target's fields as their own" do
        assert @a_alias.field?(:a_field1)
        @a_alias.a_field1.value.must_equal a.a_field1.value
      end

      it "have access to their target's fields" do
        @a_alias.target.a_field1.value.must_equal a.a_field1.value
      end

      it "have their own styles" do
        assert_correct_template(@a_alias,  template_root / 'a_alias/a_alias_style', renderer)
      end

      it "present their target's styles as their own" do
        @a_alias.style = :a_style

        assert_correct_template(@a_alias,  template_root / 'a/a_style', renderer)
      end

      it "should use templates belonging to the alias class if they exist" do
        assert_correct_template(@aa_alias,  template_root / 'aa_alias', renderer)
      end

      it "should fallback to the targets default style if no alias template is present" do
        assert_correct_template(@aaa_alias,  aaa1.template, renderer)
      end

      # should "have an independent style setting"
      it "not delete their target when deleted" do
        @a_alias.destroy
        Content[a.id].must_equal a
      end

      it "be deleted when target deleted" do
        a.destroy
        Content[@a_alias.id].must_be_nil
      end

      it "include target values in serialisation" do
        @a_alias.export[:target].must_equal a.shallow_export(nil)
      end

      it "include alias title & icon in serialisation" do
        @a_alias.export[:alias_title].must_equal a.alias_title
        @a_alias.export[:alias_icon].must_equal a.alias_icon_field.export
      end
    end
  end

  describe "Aliases to custom models" do
    before do
      @target_id = target_id = 9999
      @target = target = mock()
      @target.stubs(:id).returns(@target_id)
      @target.stubs(:title).returns("custom object")

      @custom_alias_class = Class.new(::Page) do
        alias_of proc { [target] }, :lookup => lambda { |id|
          return target if id == target_id
          nil
        }, :slug => lambda { |target| target.title.to_url }
      end
    end
    it "be creatable using a custom initializer" do
      a = @custom_alias_class.for_target(@target_id)

      a.target_id.must_equal @target_id
      a.target.must_equal @target
    end

    it "be able to provide a slug for pages" do
      a = @custom_alias_class.for_target(@target_id)
      a.target.must_equal @target
      a.slug.must_equal "custom-object"
    end

    it "ignore styles if object doesn't provide them" do
      a = @custom_alias_class.for_target(@target_id)
      a.style.template.call.must_equal Page.new.style.template.call
    end
  end


  describe "Piece aliases" do
    it "be allowed to target pages" do
      a = BBAlias.create(:target => bb)
      a.bb_field1.value.must_equal "BB"
    end

    it "not be loadable via their compound path when linked to a page" do
      a = BBAlias.create(:target => bb)
      aliases.box1 << a
      aliases.save
      site["/aliases/bb"].must_be_nil
    end

    it "have their target's path attribute if they alias to a page type" do
      a = BBAlias.create(:target => bb)
      a.path.must_equal bb.path
    end
  end

  describe "Page aliases" do
    before do
      BAlias.instance_variable_set(:@layout_prototypes, nil)
      aliases.reload
      b.reload
    end
    it "be allowed to have piece classes as targets" do
      class ::CAlias < Page
        alias_of :AAA
        layout :c_alias
      end

      c = CAlias.new(:target => aaa1)
      c.render.must_equal "aaa1\n"
    end

    it "respond as a page" do
      a = BAlias.create(:target => b, :slug => "balias")
      assert a.page?
    end

    it "be discoverable via their compound path" do
      a = BAlias.create(:target => b, :slug => "balias")
      aliases.box1 << a
      aliases.save
      a.save
      a.reload
      a.path.must_equal "/aliases/b"
      site["/aliases/balias"].must_be_nil
      site["/aliases/b"].must_equal a
    end

    it "update their path if their target's slug changes" do
      al = BAlias.create(:target => b, :slug => "balias")
      bl = BAlias.create(:target => b, :slug => "balias")
      aliases.box1 << al
      al.box1 << bl
      aliases.save

      al.save
      al.reload
      al.path.must_equal "/aliases/b"
      bl.path.must_equal "/aliases/b/b"
      b.slug = "newb"
      b.save
      al.reload
      bl.reload
      al.path.must_equal "/aliases/newb"
      bl.path.must_equal "/aliases/newb/newb"
    end

    it "update their path if their parent's path changes" do
      al = BAlias.create(:target => b, :slug => "balias")
      bl = BAlias.create(:target => b, :slug => "balias")
      aliases.box1 << al
      al.box1 << bl
      aliases.save
      al.save
      al.reload
      al.path.must_equal "/aliases/b"
      bl.path.must_equal "/aliases/b/b"
      aliases.slug = "newaliases"
      aliases.save
      al.reload
      bl.reload
      al.path.must_equal "/newaliases/b"
      bl.path.must_equal "/newaliases/b/b"
    end

    it "show in the parent's list of children" do
      al = BAlias.create(:target => b, :slug => "balias")
      aliases.box1 << al
      aliases.save
      al.save
      al.reload
      aliases.reload
      aliases.children.must_equal [al]
      al.parent.must_equal aliases
    end

    it "render the using target's layout when accessed via the path and no local layouts defined" do
      al = BAlias.create(:target => b, :slug => "balias")
      aliases.box1 << al
      aliases.save
      al.reload
      al.render.must_equal b.render
    end

    it "render with locally defined style when available" do
      BAlias.layout :b_alias
      al = BAlias.create(:target => b, :slug => "balias")
      aliases.box1 << al
      aliases.save
      al.reload
      al.render.must_equal "alternate\n"
    end

    it "have access to their target's page styles" do
      BAlias.layout :b_alias
      a = BAlias.create(:target => b, :slug => "balias")
      aliases.box1 << a
      aliases.save
      a.reload
      a.layout = :b
      a.render.must_equal b.render
    end
  end

  describe "visibility" do
    it "be linked to the target's visibility" do
      a = BAlias.create(:target => b, :slug => "balias")
      b.hide!
      b.reload
      a.reload
      refute a.visible?
    end
  end
end