# frozen_string_literal: true

require_relative "test_helper"

module SassC
  module NativeTest
    SAMPLE_SASS_STRING = "$size: 30px; .hi { width: $size; }"
    SPECIAL_SASS_STRING = "$sißßßßßße: 30px; .hßß©i { width: $size; }"
    SAMPLE_CSS_OUTPUT = ".hi {\n  width: 30px; }\n"
    BAD_SASS_STRING = "$size = 30px;"

    class General < MiniTest::Test
      def test_it_reports_the_libsass_version
        assert_equal "3.6.3", Native.version
      end
    end

    class DataContext < MiniTest::Test
      def teardown
        Native.delete_data_context(@data_context) if @data_context
      end

      def test_compile_status_is_zero_when_successful
        @data_context = Native.make_data_context(SAMPLE_SASS_STRING)
        context = Native.data_context_get_context(@data_context)

        status = Native.compile_data_context(@data_context)
        assert_equal 0, status

        status = Native.context_get_error_status(context)
        assert_equal 0, status
      end

      def test_compiled_css_is_correct
        @data_context = Native.make_data_context(SAMPLE_SASS_STRING)
        context = Native.data_context_get_context(@data_context)
        Native.compile_data_context(@data_context)

        css = Native.context_get_output_string(context)
        assert_equal SAMPLE_CSS_OUTPUT, css
      end

      def test_compile_status_is_one_if_failed
        @data_context = Native.make_data_context(BAD_SASS_STRING)
        context = Native.data_context_get_context(@data_context)

        status = Native.compile_data_context(@data_context)
        refute_equal 0, status

        status = Native.context_get_error_status(context)
        refute_equal 0, status
      end

      def test_multibyte_characters_work
        @data_context = Native.make_data_context(SPECIAL_SASS_STRING)
        context = Native.data_context_get_context(@data_context)

        status = Native.compile_data_context(@data_context)
        refute_equal 0, status
      end

      def test_custom_function
        data_context = Native.make_data_context("foo { margin: foo(); }")
        context = Native.data_context_get_context(data_context)
        options = Native.context_get_options(context)

        random_thing = FFI::MemoryPointer.from_string("hi")

        funct = FFI::Function.new(:pointer, [:pointer, :pointer]) do |s_args, cookie|
          Native.make_number(43, "px")
        end

        callback = Native.make_function(
          "foo()",
          funct,
          random_thing
        )

        list = Native.make_function_list(1)
        Native::function_set_list_entry(list, 0, callback);
        Native::option_set_c_functions(options, list)

        assert_equal Native.option_get_c_functions(options), list

        first_list_entry = Native.function_get_list_entry(list, 0)
        assert_equal Native.function_get_function(first_list_entry),
                     funct
        assert_equal Native.function_get_signature(first_list_entry),
                     "foo()"
        assert_equal Native.function_get_cookie(first_list_entry),
                     random_thing

        string = Native.make_string("hello")
        assert_equal :sass_string, Native.value_get_tag(string)
        assert_equal "hello", Native.string_get_value(string)

        number = Native.make_number(123.4, "rem")
        assert_equal 123.4, Native.number_get_value(number)
        assert_equal "rem", Native.number_get_unit(number)

        Native.compile_data_context(data_context)

        css = Native.context_get_output_string(context)
        assert_equal "foo {\n  margin: 43px; }\n", css
      end
    end

    class FileContext < MiniTest::Test
      include TempFileTest

      def teardown
        Native.delete_file_context(@file_context) if @file_context
      end

      def test_compile_status_is_zero_when_successful
        temp_file("style.scss", SAMPLE_SASS_STRING)

        @file_context = Native.make_file_context("style.scss")
        context = Native.file_context_get_context(@file_context)

        status = Native.compile_file_context(@file_context)
        assert_equal 0, status

        status = Native.context_get_error_status(context)
        assert_equal 0, status
      end

      def test_compiled_css_is_correct
        temp_file("style.scss", SAMPLE_SASS_STRING)

        @file_context = Native.make_file_context("style.scss")
        context = Native.file_context_get_context(@file_context)
        Native.compile_file_context(@file_context)

        css = Native.context_get_output_string(context)
        assert_equal SAMPLE_CSS_OUTPUT, css
      end

      def test_invalid_file_name
        temp_file("style.scss", SAMPLE_SASS_STRING)

        @file_context = Native.make_file_context("style.jajaja")
        context = Native.file_context_get_context(@file_context)
        status = Native.compile_file_context(@file_context)

        refute_equal 0, status

        error = Native.context_get_error_message(context)

        assert_match "Error: File to read not found or unreadable: style.jajaja",
                     error
      end

      def test_file_import
        temp_file("not_included.scss", "$size: 30px;")
        temp_file("import_parent.scss", "$size: 30px;")
        temp_file("import.scss", "@import 'import_parent'; $size: 30px;")
        temp_file("styles.scss", "@import 'import.scss'; .hi { width: $size; }")

        @file_context = Native.make_file_context("styles.scss")
        context = Native.file_context_get_context(@file_context)
        status = Native.compile_file_context(@file_context)

        assert_equal 0, status

        css = Native.context_get_output_string(context)
        assert_equal SAMPLE_CSS_OUTPUT, css

        included_files = Native.context_get_included_files(context)
        included_files.sort!

        assert_match /import.scss/, included_files[0]
        assert_match /import_parent.scss/, included_files[1]
        assert_match /styles.scss/, included_files[2]
      end

      def test_custom_importer
        temp_file("not_included.scss", "$size: $var + 25;")
        temp_file("styles.scss", "@import 'import.scss'; .hi { width: $size; }")

        @file_context = Native.make_file_context("styles.scss")
        context = Native.file_context_get_context(@file_context)
        options = Native.context_get_options(context)

        funct = FFI::Function.new(:pointer, [:pointer, :pointer, :pointer]) do |url, prev, cookie|
          list = Native.make_import_list(2)

          str = "$var: 5px;\0"
          data = Native::LibC.malloc(str.size)
          data.write_string(str)

          entry0 = Native.make_import_entry("fake_includ.scss", data, nil)
          entry1 = Native.make_import_entry("not_included.scss", nil, nil)
          Native.import_set_list_entry(list, 0, entry0)
          Native.import_set_list_entry(list, 1, entry1)
          list
        end

        callback = Native.make_importer(funct, nil)
        list = Native.make_function_list(1)
        Native::function_set_list_entry(list, 0, callback)

        Native.option_set_c_importers(options, list)

        status = Native.compile_file_context(@file_context)
        assert_equal 0, status

        css = Native.context_get_output_string(context)
        assert_equal SAMPLE_CSS_OUTPUT, css
      end
    end
  end
end
