# frozen_string_literal: true

require "ffi"

module BarcodePao
  FORMAT_PNG  = "png"
  FORMAT_JPEG = "jpeg"
  FORMAT_SVG  = "svg"

  # Barcode type IDs (matching barcode_ffi.h)
  BC_CODE39                = 0
  BC_CODE93                = 1
  BC_CODE128               = 2
  BC_GS1_128               = 3
  BC_NW7                   = 4
  BC_MATRIX2OF5            = 5
  BC_NEC2OF5               = 6
  BC_JAN8                  = 7
  BC_JAN13                 = 8
  BC_UPC_A                 = 9
  BC_UPC_E                 = 10
  BC_ITF                   = 11
  BC_GS1_DATABAR_14        = 12
  BC_GS1_DATABAR_LIMITED   = 13
  BC_GS1_DATABAR_EXPANDED  = 14
  BC_YUBIN_CUSTOMER        = 15
  BC_QR                    = 16
  BC_DATAMATRIX            = 17
  BC_PDF417                = 18

  # --- Native FFI Bindings ---
  module Native
    extend FFI::Library

    def self.find_library
      native_dir = File.join(File.dirname(__FILE__), "native")
      if FFI::Platform.windows?
        # Preload dependent DLLs so barcode_pao.dll can find them
        %w[SDL2.dll SDL2_image.dll SDL2_ttf.dll].each do |dep|
          dep_path = File.join(native_dir, dep)
          FFI::DynamicLibrary.open(dep_path, FFI::DynamicLibrary::RTLD_GLOBAL) if File.exist?(dep_path)
        end
        File.join(native_dir, "barcode_pao.dll")
      elsif FFI::Platform.mac?
        File.join(native_dir, "libbarcode_pao.dylib")
      else
        File.join(native_dir, "libbarcode_pao.so")
      end
    end

    ffi_lib find_library

    # Create / Destroy
    attach_function :barcode_create, [:int], :pointer
    attach_function :barcode_destroy, [:pointer], :void

    # Common settings
    attach_function :barcode_set_output_format, [:pointer, :string], :void
    attach_function :barcode_set_foreground_color, [:pointer, :int, :int, :int, :int], :void
    attach_function :barcode_set_background_color, [:pointer, :int, :int, :int, :int], :void
    attach_function :barcode_set_px_adjust_black, [:pointer, :int], :void
    attach_function :barcode_set_px_adjust_white, [:pointer, :int], :void
    attach_function :barcode_set_fit_width, [:pointer, :int], :void

    # 1D settings
    attach_function :barcode_set_show_text, [:pointer, :int], :void
    attach_function :barcode_set_text_font_scale, [:pointer, :double], :void
    attach_function :barcode_set_text_gap, [:pointer, :double], :void
    attach_function :barcode_set_text_even_spacing, [:pointer, :int], :void

    # 2D settings
    attach_function :barcode_set_string_encoding, [:pointer, :string], :void

    # Type-specific settings
    attach_function :barcode_set_show_start_stop, [:pointer, :int], :void
    attach_function :barcode_set_code_mode, [:pointer, :string], :void
    attach_function :barcode_set_extended_guard, [:pointer, :int], :void
    attach_function :barcode_set_error_correction_level, [:pointer, :string], :void
    attach_function :barcode_set_version, [:pointer, :int], :void
    attach_function :barcode_set_encode_mode, [:pointer, :string], :void
    attach_function :barcode_set_code_size, [:pointer, :string], :void
    attach_function :barcode_set_encode_scheme, [:pointer, :string], :void
    attach_function :barcode_set_error_level, [:pointer, :int], :void
    attach_function :barcode_set_columns, [:pointer, :int], :void
    attach_function :barcode_set_rows, [:pointer, :int], :void
    attach_function :barcode_set_aspect_ratio, [:pointer, :double], :void
    attach_function :barcode_set_y_height, [:pointer, :int], :void
    attach_function :barcode_set_symbol_type_14, [:pointer, :string], :void
    attach_function :barcode_set_symbol_type_exp, [:pointer, :string], :void
    attach_function :barcode_set_no_of_columns, [:pointer, :int], :void

    # Draw
    attach_function :barcode_draw_1d, [:pointer, :string, :int, :int], :int
    attach_function :barcode_draw_2d, [:pointer, :string, :int], :int
    attach_function :barcode_draw_2d_rect, [:pointer, :string, :int, :int], :int
    attach_function :barcode_draw_yubin, [:pointer, :string, :int], :int

    # Get results
    attach_function :barcode_get_base64, [:pointer], :string
    attach_function :barcode_get_svg, [:pointer], :string
    attach_function :barcode_is_svg_output, [:pointer], :int
  end

  # --- Base class with handle lifecycle ---
  class BarcodeBase
    attr_reader :output_format

    def initialize(type_id, output_format = FORMAT_PNG)
      @handle = Native.barcode_create(type_id)
      raise "failed to create barcode handle for type #{type_id}" if @handle.null?
      @output_format = output_format
      Native.barcode_set_output_format(@handle, output_format)
      ObjectSpace.define_finalizer(self, BarcodeBase.release(@handle))
    end

    def self.release(handle)
      proc { Native.barcode_destroy(handle) }
    end

    def set_output_format(fmt)
      @output_format = fmt
      Native.barcode_set_output_format(@handle, fmt)
    end

    def set_foreground_color(r, g, b, a = 255)
      Native.barcode_set_foreground_color(@handle, r, g, b, a)
    end

    def set_background_color(r, g, b, a = 255)
      Native.barcode_set_background_color(@handle, r, g, b, a)
    end

    def set_px_adjust_black(val)
      Native.barcode_set_px_adjust_black(@handle, val)
    end

    def set_px_adjust_white(val)
      Native.barcode_set_px_adjust_white(@handle, val)
    end

    def set_fit_width(fit)
      Native.barcode_set_fit_width(@handle, fit ? 1 : 0)
    end

    def get_image_base64
      Native.barcode_get_base64(@handle)
    end

    def get_svg
      Native.barcode_get_svg(@handle)
    end

    def svg_output?
      Native.barcode_is_svg_output(@handle) == 1
    end
  end

  # --- 1D base ---
  class BarcodeBase1D < BarcodeBase
    def set_show_text(show)
      Native.barcode_set_show_text(@handle, show ? 1 : 0)
    end

    def set_text_font_scale(scale)
      Native.barcode_set_text_font_scale(@handle, scale.to_f)
    end

    def set_text_gap(scale)
      Native.barcode_set_text_gap(@handle, scale.to_f)
    end

    def set_text_even_spacing(even)
      Native.barcode_set_text_even_spacing(@handle, even ? 1 : 0)
    end

    def draw(code, width, height)
      result = Native.barcode_draw_1d(@handle, code, width, height)
      raise "draw failed" if result != 1
    end
  end

  # --- 2D base ---
  class BarcodeBase2D < BarcodeBase
    def set_string_encoding(encoding)
      Native.barcode_set_string_encoding(@handle, encoding)
    end

    def draw(code, size)
      result = Native.barcode_draw_2d(@handle, code, size)
      raise "draw failed" if result != 1
    end
  end

  # --- Concrete 1D types ---

  class Code39 < BarcodeBase1D
    def initialize(output_format = FORMAT_PNG)
      super(BC_CODE39, output_format)
    end

    def set_show_start_stop(show)
      Native.barcode_set_show_start_stop(@handle, show ? 1 : 0)
    end
  end

  class Code93 < BarcodeBase1D
    def initialize(output_format = FORMAT_PNG)
      super(BC_CODE93, output_format)
    end
  end

  class Code128 < BarcodeBase1D
    def initialize(output_format = FORMAT_PNG)
      super(BC_CODE128, output_format)
    end

    def set_code_mode(mode)
      Native.barcode_set_code_mode(@handle, mode)
    end
  end

  class GS1128 < BarcodeBase1D
    def initialize(output_format = FORMAT_PNG)
      super(BC_GS1_128, output_format)
    end
  end

  class NW7 < BarcodeBase1D
    def initialize(output_format = FORMAT_PNG)
      super(BC_NW7, output_format)
    end

    def set_show_start_stop(show)
      Native.barcode_set_show_start_stop(@handle, show ? 1 : 0)
    end
  end

  class ITF < BarcodeBase1D
    def initialize(output_format = FORMAT_PNG)
      super(BC_ITF, output_format)
    end
  end

  class Matrix2of5 < BarcodeBase1D
    def initialize(output_format = FORMAT_PNG)
      super(BC_MATRIX2OF5, output_format)
    end
  end

  class NEC2of5 < BarcodeBase1D
    def initialize(output_format = FORMAT_PNG)
      super(BC_NEC2OF5, output_format)
    end
  end

  class JAN8 < BarcodeBase1D
    def initialize(output_format = FORMAT_PNG)
      super(BC_JAN8, output_format)
    end

    def set_extended_guard(ext)
      Native.barcode_set_extended_guard(@handle, ext ? 1 : 0)
    end
  end

  class JAN13 < BarcodeBase1D
    def initialize(output_format = FORMAT_PNG)
      super(BC_JAN13, output_format)
    end

    def set_extended_guard(ext)
      Native.barcode_set_extended_guard(@handle, ext ? 1 : 0)
    end
  end

  class UPCA < BarcodeBase1D
    def initialize(output_format = FORMAT_PNG)
      super(BC_UPC_A, output_format)
    end

    def set_extended_guard(ext)
      Native.barcode_set_extended_guard(@handle, ext ? 1 : 0)
    end
  end

  class UPCE < BarcodeBase1D
    def initialize(output_format = FORMAT_PNG)
      super(BC_UPC_E, output_format)
    end

    def set_extended_guard(ext)
      Native.barcode_set_extended_guard(@handle, ext ? 1 : 0)
    end
  end

  class GS1DataBar14 < BarcodeBase1D
    def initialize(output_format = FORMAT_PNG)
      super(BC_GS1_DATABAR_14, output_format)
    end

    def set_symbol_type(type_str)
      Native.barcode_set_symbol_type_14(@handle, type_str)
    end
  end

  class GS1DataBarLimited < BarcodeBase1D
    def initialize(output_format = FORMAT_PNG)
      super(BC_GS1_DATABAR_LIMITED, output_format)
    end
  end

  class GS1DataBarExpanded < BarcodeBase1D
    def initialize(output_format = FORMAT_PNG)
      super(BC_GS1_DATABAR_EXPANDED, output_format)
    end

    def set_symbol_type(type_str)
      Native.barcode_set_symbol_type_exp(@handle, type_str)
    end

    def set_no_of_columns(cols)
      Native.barcode_set_no_of_columns(@handle, cols)
    end
  end

  class YubinCustomer < BarcodeBase
    def initialize(output_format = FORMAT_PNG)
      super(BC_YUBIN_CUSTOMER, output_format)
    end

    def draw(code, height)
      result = Native.barcode_draw_yubin(@handle, code, height)
      raise "draw failed" if result != 1
    end
  end

  # --- 2D types ---

  class QRCode < BarcodeBase2D
    def initialize(output_format = FORMAT_PNG)
      super(BC_QR, output_format)
    end

    def set_error_correction_level(level)
      Native.barcode_set_error_correction_level(@handle, level)
    end

    def set_version(version)
      Native.barcode_set_version(@handle, version)
    end

    def set_encode_mode(mode)
      Native.barcode_set_encode_mode(@handle, mode)
    end
  end

  class DataMatrix < BarcodeBase2D
    def initialize(output_format = FORMAT_PNG)
      super(BC_DATAMATRIX, output_format)
    end

    def set_code_size(size)
      Native.barcode_set_code_size(@handle, size)
    end

    def set_encode_scheme(scheme)
      Native.barcode_set_encode_scheme(@handle, scheme)
    end
  end

  class PDF417 < BarcodeBase2D
    def initialize(output_format = FORMAT_PNG)
      super(BC_PDF417, output_format)
    end

    def set_error_level(level)
      Native.barcode_set_error_level(@handle, level)
    end

    def set_columns(cols)
      Native.barcode_set_columns(@handle, cols)
    end

    def set_rows(rows)
      Native.barcode_set_rows(@handle, rows)
    end

    def set_aspect_ratio(ratio)
      Native.barcode_set_aspect_ratio(@handle, ratio.to_f)
    end

    def set_y_height(y)
      Native.barcode_set_y_height(@handle, y)
    end

    def draw(code, width, height)
      result = Native.barcode_draw_2d_rect(@handle, code, width, height)
      raise "draw failed" if result != 1
    end
  end
end
