# encoding: utf-8
require 'test_helper'

class DirectorCell < Cell::ViewModel
  attr_reader :count

  def initialize(*)
    super
    @count = 0
  end

  cache :tock

  def tock
    @count += 1
  end
end


class CachingUnitTest < Minitest::Spec
  before :each do
    ActionController::Base.cache_store.clear
    ActionController::Base.perform_caching = true
  end

  let (:director) { DirectorCell }
  let (:cellule) { DirectorCell.new(nil) }


  describe "::state_cache_key" do
    # accepts state name, only.
    it { _(director.state_cache_key(:count)).must_equal "cells/director/count/" }

    # accepts hash as key parts
    if Cell.rails_version >= Gem::Version.new('4.0')
      it { _(director.state_cache_key(:count, b: 2, a: 1)).must_equal "cells/director/count/b/2/a/1" }
    else
      it { _(director.state_cache_key(:count, b: 2, a: 1)).must_equal "cells/director/count/a=1&b=2" }
    end

    # accepts array as key parts
    it { _(director.state_cache_key(:count, [1, 2, 3])).must_equal "cells/director/count/1/2/3" }

    # accepts string as key parts
    it { _(director.state_cache_key(:count, "1/2")).must_equal "cells/director/count/1/2" }

    # accepts nil as key parts
    it { _(director.state_cache_key(:count, nil)).must_equal "cells/director/count/" }
  end


  describe "#state_cached?" do
    # true for cached
    it { _(cellule.send(:state_cached?, :tock)).must_equal true }

    # false otherwise
    it { _(cellule.send(:state_cached?, :sing)).must_equal false }
  end


  describe "#cache?" do
    # true for cached
    it { _(cellule.cache?(:tock)).must_equal true }

    # false otherwise
    it { _(cellule.cache?(:sing)).must_equal false }

    describe "perform_caching turned off" do
      after do
        ::ActionController::Base.perform_caching = true
      end

      # always false
      it do
        ::ActionController::Base.perform_caching = false
        _(cellule.cache?(:sing)).must_equal false
        _(cellule.cache?(:sing)).must_equal false
      end
    end

    describe "#cache_store" do
      # rails cache store per default.
      it { _(cellule.cache_store).must_equal ActionController::Base.cache_store }
    end
  end


  # describe ".expire_cache_key" do
  #   before :each do
  #     @key = @class.state_cache_key(:tock)
  #     puts "====== key is #{@key}"
  #     assert_equal "1", render_cell(:director, :tock)
  #     assert_equal "1", @class.cache_store.read(@key)
  #   end

  #   it "delete the state from cache" do
  #     @class.expire_cache_key(@key)
  #     assert_not @class.cache_store.read(@key)
  #   end

  #   it "be available in controllers for sweepers" do
  #     MusicianController.new.expire_cell_state(DirectorCell, :tock)
  #     assert_not @class.cache_store.read(@key)
  #   end

  #   it "accept cache options" do
  #     key = @class.state_cache_key(:tock, volume: 9)
  #     assert Cell::Rails.cache_store.write(key, 'ONE!')

  #     MusicianController.new.expire_cell_state(DirectorCell, :tock, volume: 9)
  #     assert_equal "1", @class.cache_store.read(@key)
  #     assert_not ::Cell::Rails.cache_store.read(key)
  #   end
  # end
end


class CachingTest < Minitest::Spec
  class DirectorCell < Cell::ViewModel
    def initialize(counter=0)
      super
      @counter = counter
    end

    def show # public method.
      @counter
    end


    cache :utf8

    def utf8
      "æøå" # or any other UTF-8 string
    end
  end

  before :each do
    ActionController::Base.cache_store.clear
    ActionController::Base.perform_caching = true
  end

  # let (:cell) { DirectorCell.new(nil) }
  def director_cell(*args)
    DirectorCell.new(*args)
  end

  # no caching when turned off.
  it do
    director_cell.class.cache :show
    ActionController::Base.perform_caching = false

    _(director_cell(1).call).must_equal "1"
    _(director_cell(2).call).must_equal "2"
  end

  # cache forever when no options.
  it do
    director_cell.class.cache :show
    _(director_cell(1).call).must_equal "1"
    _(director_cell(2).call).must_equal "1"
  end

   # caches with string state name.
  it do
    director_cell.class.cache :show
    _(director_cell(1).("show")).must_equal "1"
    _(director_cell(2).("show")).must_equal "1"
  end


  # no caching when state not configured.
  it do
    director_cell.class.class_eval do
      def dictate
        @counter
      end
    end

    _(director_cell(1).call(:dictate)).must_equal "1"
    _(director_cell(2).call(:dictate)).must_equal "2"
  end

  # compute key with cell properties from #initialize.
  it do
    director_cell.class.cache :show do
      @counter < 3 ? {count: "<"} : {count: ">"}
    end

    _(director_cell(1).call).must_equal "1"
    _(director_cell(2).call).must_equal "1"
    _(director_cell(3).call).must_equal "3"
    _(director_cell(4).call).must_equal "3"
  end

  # compute key with instance method
  it do
    director_cell.class.cache :show, :version
    director_cell.class.class_eval do
      def version
        @counter < 3 ? {count: "<"} : {count: ">"}
      end
    end

    _(director_cell(1).call).must_equal "1"
    _(director_cell(2).call).must_equal "1"
    _(director_cell(3).call).must_equal "3"
    _(director_cell(4).call).must_equal "3"
  end

  # allow returning strings for key
  it do
    director_cell.class.cache :show do
      @counter < 3 ? "<" : ">"
    end

    _(director_cell(1).call).must_equal "1"
    _(director_cell(2).call).must_equal "1"
    _(director_cell(3).call).must_equal "3"
    _(director_cell(4).call).must_equal "3"
  end

  # allows conditional ifs.
  it do
    director_cell.class.cache :show, if: lambda { @counter < 3 }

    _(director_cell(1).call).must_equal "1"
    _(director_cell(2).call).must_equal "1"
    _(director_cell(3).call).must_equal "3"
    _(director_cell(4).call).must_equal "4"
  end

  # allows conditional ifs with instance method.
  it do
    director_cell.class.class_eval do
      cache :show, if: :smaller?

      def smaller?
        @counter < 3
      end
    end

    _(director_cell(1).call).must_equal "1"
    _(director_cell(2).call).must_equal "1"
    _(director_cell(3).call).must_equal "3"
    _(director_cell(4).call).must_equal "4"
  end


  unless ::ActionPack::VERSION::MAJOR == 3 and ::ActionPack::VERSION::MINOR >= 2 # bug in 3.2.
    describe "utf-8" do
      before do
        @key = director_cell.class.state_cache_key(:utf8)
      end

      it "has the correct encoding when reading from cache" do
        assert_equal "UTF-8", director_cell.call(:utf8).encoding.to_s
        assert_equal "UTF-8", director_cell.cache_store.read(@key).encoding.to_s
      end
    end
  end

  # options are passed through to cache store.
  # :expires_in.
  # tags: lambda { |one, two, three| "#{one},#{two},#{three}" }
  class CacheStore
    attr_reader :fetch_args

    def fetch(*args)
      @fetch_args = args
    end
  end

  it do
    cellule = self.director_cell

    cellule.instance_eval do
      def cache_store;
        @cache_store ||= CacheStore.new;
      end
    end

    cellule.class.cache :show, expires_in: 1.minutes, tags: lambda { self.class.to_s }
    cellule.call
    _(cellule.cache_store.fetch_args).must_equal ["cells/caching_test/director/show/", {expires_in: 60, tags: "CachingTest::DirectorCell"}]
  end
end


class CachingInheritanceTest < CachingTest
  class DirectorCell < ::DirectorCell
    cache :show, expires_in: 10.minutes do
      "v1"
    end
  end

  class DirectorsSonCell < DirectorCell
  end

  class DirectorsDaughterCell < ::DirectorCell
    cache :show, expires_in: 9.minutes do
      "v2"
    end
  end

end
