# frozen_string_literal: true

require 'spec_helper'
require 'ttfunk/table/os2'
require 'ttfunk/subset'

RSpec.describe TTFunk::Table::OS2 do
  subject(:os2) { font.os2 }

  let(:bits_to_blocks) do
    described_class::UNICODE_BLOCKS.each_with_object({}) do |(range, bit), ret|
      ret[bit] ||= []
      ret[bit] << range
    end
  end

  # unicode char_range bits
  let(:greek) { 7 } # Greek and Coptic
  let(:armenian) { 10 } # Armenian
  let(:hebrew) { 11 } # Hebrew
  let(:thai) { 24 } # Thai
  let(:greek_ext) { 30 } # Greek extended
  let(:curr_symbols) { 33 } # currency symbols
  let(:shapes) { 45 } # geometric shapes
  let(:ogham) { 78 } # Ogham
  let(:old_italic) { 85 } # Old Italic

  let(:char_range_field_indices) { 42...58 }
  let(:code_page_range_field_indices) { 78...86 }

  let(:font_path) { test_font('DejaVuSans') }
  let(:font) { TTFunk::File.open(font_path) }

  let(:expected_fields) do
    {
      version: 1,
      ave_char_width: 1038,
      weight_class: 400,
      width_class: 5,
      type: 0,
      y_subscript_x_size: 1331,
      y_subscript_y_size: 1433,
      y_subscript_x_offset: 0,
      y_subscript_y_offset: 286,
      y_superscript_x_size: 1331,
      y_superscript_y_size: 1433,
      y_superscript_x_offset: 0,
      y_superscript_y_offset: 983,
      y_strikeout_size: 102,
      y_strikeout_position: 530,
      family_class: 0,
      panose: "\x02\v\x06\x03\x03\b\x04\x02\x02\x04",
      char_range: 3_138_825_546_350_813_869_068_267_263,
      vendor_id: 'PfEd',
      selection: 64,
      first_char_index: 32,
      last_char_index: 65_535,
      ascent: 1556,
      descent: -492,
      line_gap: 410,
      win_ascent: 1901,
      win_descent: 483,
      code_page_range: 16_140_619_591_129_760_255,

      # require os2 version 2 or above
      x_height: nil,
      cap_height: nil,
      default_char: nil,
      break_char: nil,
      max_context: nil,
    }
  end

  let(:error_message) do
    <<~ERROR_MESSAGE
         field: %<field>s
      expected: %<expected_value>s
           got: %<actual_value>s
    ERROR_MESSAGE
  end

  def build_error_message(field, expected_value, actual_value)
    format(
      error_message,
      field: field,
      expected_value: expected_value,
      actual_value: actual_value,
    )
  end

  it 'parses all fields correctly' do
    expected_fields.each do |field, expected_value|
      actual_value = os2.public_send(field)
      actual_value = actual_value.value if actual_value.respond_to?(:value)
      expect(actual_value).to(
        eq(expected_value),
        build_error_message(field, expected_value, actual_value),
      )
    end
  end

  describe '.encode' do
    let(:encoded) { subject.class.encode(subject, subset) }

    let(:code_page_range) do
      TTFunk::BitField.new(
        TTFunk::BinUtils.stitch_int(
          encoded[code_page_range_field_indices].unpack('N*'),
          bit_width: 32,
        ),
      )
    end

    let(:char_range) do
      TTFunk::BitField.new(
        TTFunk::BinUtils.stitch_int(
          encoded[char_range_field_indices].unpack('N*'),
          bit_width: 32,
        ),
      )
    end

    context 'with a unicode subset' do
      let(:subset) { TTFunk::Subset::Unicode.new(font) }
      let(:original_unicode_map) { font.cmap.unicode.first.code_map }

      it 'roundtrips correctly' do
        original_unicode_map.each_key { |char| subset.use(char) }
        reconstituted = described_class.new(TestFile.new(StringIO.new(encoded)))

        skipped_fields = %i[char_range code_page_range]
        expected_fields.each do |field, expected_value|
          actual_value = reconstituted.public_send(field)

          # check these fields manually (they have been recalculated)
          next if skipped_fields.include?(field)

          expect(actual_value).to(
            eq(expected_value),
            build_error_message(field, expected_value, actual_value),
          )
        end

        # since we're asking for a unicode subset, code_page_range should
        # be zero
        expect(reconstituted.code_page_range.value).to eq(0)

        # check several of the bits in char_range, which specifies unicode
        # blocks the font supports
        char_range_bits = [
          greek,
          armenian,
          hebrew,
          thai,
          greek_ext,
          curr_symbols,
          shapes,
          ogham,
          old_italic,
        ]

        char_range_bits.each do |bit|
          expect(reconstituted.char_range.on?(bit)).to(be(true), build_error_message(bit, true, false))
        end
      end

      it 'ensures char_range only includes the blocks in the subset' do
        # Armenian, geometric shapes
        [armenian, shapes].each do |bit|
          bits_to_blocks[bit].each do |range|
            range.each { |code_point| subset.use(code_point) }
          end
        end

        expect(char_range.on?(greek)).to be false
        expect(char_range.on?(armenian)).to be true
        expect(char_range.on?(hebrew)).to be false
        expect(char_range.on?(shapes)).to be true
        expect(char_range.on?(old_italic)).to be false
      end
    end

    context 'with a mac roman subset' do
      let(:subset) { TTFunk::Subset::MacRoman.new(font) }
      let(:mac_roman_code_page_bit) { 29 }

      before do
        mapping = TTFunk::Subset::CodePage.unicode_mapping_for(Encoding::MACROMAN)

        mapping.each_value { |code_point| subset.use(code_point) }
      end

      it 'does not set any char_range (unicode) bits' do
        expect(char_range.value).to eq(0)
      end

      it 'sets the correct code page bit' do
        expect(code_page_range.on?(mac_roman_code_page_bit)).to be true
      end
    end

    context 'with a windows 1252 subset' do
      let(:subset) { TTFunk::Subset::Windows1252.new(font) }
      let(:windows_1252_code_page_bit) { 0 }

      before do
        mapping = TTFunk::Subset::CodePage.unicode_mapping_for(Encoding::CP1252)

        mapping.each_value { |code_point| subset.use(code_point) }
      end

      it 'does not set any char_range (unicode) bits' do
        expect(char_range.value).to eq(0)
      end

      it 'sets the correct code page bit' do
        expect(code_page_range.on?(windows_1252_code_page_bit)).to be true
      end
    end
  end
end
