require 'opal'
require 'opal-rspec'
require 'reactive_record_config'
require 'react' #_js_test_only'
require 'hyper-operation'
require 'hyper-mesh'
require 'jquery'
require 'opal-jquery'
require 'jquery.cookie'
require 'models'

Document.ready? do
  `$.cookie('acting_user', null, { path: '/' })`
  Opal::RSpec::Runner.autorun rescue nil
end

def sequenced_asyncs?
  return true
  #Opal::RSpec::Runner.method_defined?(auto_run)
  #ruby_version = RUBY_ENGINE_VERSION.split(".")
  #ruby_version[0].to_i > 0 or ruby_version[1].to_i > 8
end

class Object

  def use_case(*args, &block)
    describe(*args) do
      clear_opal_rspec_runners
      instance_eval &block
      last_promise = nil
      it "test starting" do
        expect(true).to be_truthy
      end
      runners = opal_rspec_runners
      runners = runners.reverse unless sequenced_asyncs?
      runners.each do |type, title, opts, block, promise|
        promise_to_resolve = last_promise
        async(title, opts) do
          promise.then do
            message = "               running #{type.gsub('_',' ')} #{title}"
            `console.warn(#{message})`
            Opal::RSpec::AsyncHelpers::ClassMethods.set_current_promise self, promise_to_resolve
            begin
              instance_eval &block if block
            rescue Exception => e
              message = "Failed to run #{type} #{title}\nTest raised exception before starting test block: #{e}"
              `console.error(#{message})`
            end
          end
        end
        last_promise = promise
        last_promise.resolve if sequenced_asyncs?
      end
    last_promise.resolve unless sequenced_asyncs?
    end
  end

end

module Opal
  module RSpec
    module AsyncHelpers
      module ClassMethods

        def self.set_current_promise(instance, promise)
          @current_promise = promise
          @current_promise_test_instance = instance
        end

        def self.resolve_current_promise
          @current_promise.resolve if !sequenced_asyncs? && @current_promise
        rescue Exception => e
          raise "test structure error:  Usually this is caused by a use_case test that has only a first_it an no other tests.  Check the use_case that ran just before this one."
        end

        def self.get_current_promise_test_instance
          @current_promise_test_instance
        end

        #alias_method :old_it, :it

        #def it(*args, &block)
        #  @previous_promise = new_promise
        #  old_it(*args, &block)
        #end

        def opal_rspec_runners
          @opal_rspec_runners
        end

        def clear_opal_rspec_runners
          @opal_rspec_runners = []
        end

        def opal_rspec_push_runner(type, title, opts, block)
          @opal_rspec_runners << [type, title, opts, block, Promise.new]
        end


        def first_it(title, opts = {}, &block)
          opal_rspec_push_runner("first_it", title, opts, block)
        end

        def now_it(title, opts = {}, &block)
          opal_rspec_push_runner("now_it", title, opts, block)
        end

        def and_it(title, opts = {}, &block)
          opal_rspec_push_runner("and_it", title, opts, block)
        end

        def finally(title, opts = {}, &block)
          opal_rspec_push_runner("finally", title, opts, block)
        end

        def rendering(title, &block)
          klass = Class.new do

            include React::Component

            def self.block
              @block
            end

            def self.name
              "dummy class"
            end

            backtrace :on

            def render
              instance_eval &self.class.block
            end

            def self.should_generate(opts={}, &block)
              sself = self
              @self.async(@title, opts) do
                expect_component_to_eventually(sself, &block)
              end
            end

            def self.should_immediately_generate(opts={}, &block)
              sself = self
              @self.it(@title, opts) do
                element = build_element sself, {}
                context = block.arity > 0 ? self : element
                expect((element and context.instance_exec(element, &block))).to be(true)
              end
            end

          end
          klass.instance_variable_set("@block", block)
          klass.instance_variable_set("@self", self)
          klass.instance_variable_set("@title", "it can render #{title}")
          klass
        end
      end
    end
  end
end

module ReactTestHelpers

  `var ReactTestUtils = React.addons.TestUtils`

  def renderToDocument(type, options = {})
    element = React.create_element(type, options)
    return renderElementToDocument(element)
  end

  def renderElementToDocument(element)
    instance = Native(`ReactTestUtils.renderIntoDocument(#{element.to_n})`)
    instance.class.include(React::Component::API)
    return instance
  end

  def simulateEvent(event, element, params = {})
    simulator = Native(`ReactTestUtils.Simulate`)
    #element = `#{element.to_n}.getDOMNode` unless element.class == Element
    simulator[event.to_s].call(element.dom_node, params)
    #simulator[event.to_s].call(element, params)
  end

  def isElementOfType(element, type)
    `React.addons.TestUtils.isElementOfType(#{element.to_n}, #{type.cached_component_class})`
  end

  def build_element(type, options)
    component = React.create_element(type, options)
    element = `ReactTestUtils.renderIntoDocument(#{component.to_n})`

    `ReactDOM.findDOMNode(element)` # v0.14.0 and up
  end

  def expect_component_to_eventually(component_class, opts = {}, &block)
    # Calls block after each update of a component until it returns true.  When it does set the expectation to true.
    # Uses the after_update callback of the component_class, then instantiates an element of that class
    # The call back is only called on updates, so the call back is manually called right after the
    # element is created.
    # Because React.rb runs the callback inside the components context, we have to
    # setup a lambda to get back to correct context before executing run_async.
    # Because run_async can only be run once it is protected by clearing element once the test passes.
    element = nil
    check_block = lambda do
      context = block.arity > 0 ? self : element
      run_async do
         element = nil; expect(true).to be(true)
      end if element and context.instance_exec(element, &block)
    end
    component_class.after_update { check_block.call  }
    element = build_element component_class, opts
    check_block.call
  end

  def test(&block)
    Promise.new.tap do |promise|
      promise.then_test &block
      promise.resolve
    end
  end

  # for the permissions test

  def set_acting_user(email)
    `$.cookie('acting_user', #{email}, { path: '/' })`
  end

end

class Promise

  def then_test(&block)
    self.then do |args|
      Opal::RSpec::AsyncHelpers::ClassMethods.get_current_promise_test_instance.run_async do
        yield args
        Opal::RSpec::AsyncHelpers::ClassMethods.resolve_current_promise
      end
    end
  end

  def while_waiting(&block)
    if sequenced_asyncs?
      self.then_test {Opal::RSpec::AsyncHelpers::ClassMethods.get_current_promise_test_instance.run_async { expect(true).to be_truthy }}
      block.call
    else
      self.then do
        Opal::RSpec::AsyncHelpers::ClassMethods.resolve_current_promise
      end
      Opal::RSpec::AsyncHelpers::ClassMethods.get_current_promise_test_instance.run_async &block
    end
  end

end

RSpec.configure do |config|
  config.run_all_when_everything_filtered = true
  config.filter_run_including only: true
  config.include ReactTestHelpers
  config.before(:each) do
    `current_state = {}`
  end
end
