# frozen_string_literal: true

require "json"
require "optparse"

require "libptouch"

module Libptouch
  module Cli
    # PNG のみを扱う ptouch-print 相当の CLI（1bit ラスター経路なし）。
    module PngPrint
      module_function

      def run(argv)
        opts = parse(argv)
        return 2 if opts.nil?

        return run_version if opts[:version]
        return run_help if opts[:help]

        if opts[:status]
          warn_unused_file_options(opts)
          return run_status
        end

        return usage_error("-f is required (or use --status)") if opts[:file].to_s.empty?

        path = opts[:file]
        return usage_error("not a PNG file: #{path}") unless png_file?(path)

        run_print(path, opts)
      end

      def png_file?(path)
        return true if path.downcase.end_with?(".png")

        File.open(path, "rb") do |f|
          sig = f.read(8)
          sig == "\x89PNG\r\n\x1a\n".b
        end
      rescue Errno::ENOENT, Errno::EACCES => e
        warn "open #{path}: #{e.message}"
        false
      end

      def parse(argv)
        o = {
          file: nil,
          threshold: nil,
          dry_run: false,
          status: false,
          version: false,
          help: false
        }
        parser = OptionParser.new do |p|
          p.banner = usage_banner
          p.separator ""
          p.on("-f", "--file PATH", "入力 PNG ファイル") { |v| o[:file] = v }
          p.on("-t", "--threshold N", Integer,
               "二値化しきい値 0–255（既定 #{Libptouch::PNG_DEFAULT_THRESHOLD}）") do |v|
            o[:threshold] = v
          end
          p.on("-n", "--dry-run", "読み込みと検証のみ（USB なし）") { o[:dry_run] = true }
          p.on("-S", "--status", "USB プリンタのステータスを JSON で表示して終了") { o[:status] = true }
          p.on("-V", "--version", "バージョンを表示して終了") { o[:version] = true }
          p.on("-h", "--help", "このヘルプ") { o[:help] = true }
        end
        parser.parse!(argv)

        unless o[:threshold].nil? || (0..255).cover?(o[:threshold])
          warn "-t must be 0..255"
          return nil
        end

        o
      end

      def usage_banner
        <<~BANNER
          Usage: ptouch-print-png [options]

          PNG のみ対応。幅・高さは画像から取得します（-w/-H はありません）。
          --status のときは -f は不要です。
        BANNER
      end

      def warn_unused_file_options(opts)
        return unless opts[:file] || opts[:dry_run] || !opts[:threshold].nil?

        warn "warning: options other than --status are ignored"
      end

      def run_version
        puts "ptouch-print-png #{Libptouch::VERSION}"
        0
      end

      def run_help
        puts usage_banner
        puts ""
        puts parser_help_text
        0
      end

      def parser_help_text
        o = {
          file: nil,
          threshold: nil,
          dry_run: false,
          status: false,
          version: false,
          help: false
        }
        p = OptionParser.new do |parser|
          parser.banner = "ptouch-print-png [options]"
          parser.on("-f", "--file PATH", "入力 PNG ファイル") { |v| o[:file] = v }
          parser.on("-t", "--threshold N", Integer,
                    "二値化しきい値 0–255（既定 #{Libptouch::PNG_DEFAULT_THRESHOLD}）") do |v|
            o[:threshold] = v
          end
          parser.on("-n", "--dry-run", "読み込みと検証のみ（USB なし）") { o[:dry_run] = true }
          parser.on("-S", "--status", "USB プリンタのステータスを JSON で表示して終了") { o[:status] = true }
          parser.on("-V", "--version", "バージョンを表示して終了") { o[:version] = true }
          parser.on("-h", "--help", "このヘルプ") { o[:help] = true }
        end
        p.help
      end

      def run_status
        ctx = nil
        begin
          ctx = Libptouch::Context.new
          ctx.open_usb
          h = Libptouch.parse_status(ctx.status_bytes)
          h.delete(:raw_bytes)
          puts JSON.pretty_generate(h)
          0
        rescue Libptouch::Error => e
          warn "get_status: #{e.message}"
          1
        ensure
          ctx&.dispose
        end
      end

      def run_print(path, opts)
        ctx = nil
        begin
          ctx = Libptouch::Context.new
          threshold = opts[:threshold]
          data, width, height = if threshold.nil?
                                  ctx.png_file_to_raster(path)
                                else
                                  ctx.png_file_to_raster(path, threshold: threshold)
                                end

          ctx.check_raster(data, width_dots: width, height_dots: height)

          if opts[:dry_run]
            puts "dry-run OK: #{data.bytesize} bytes, #{width}x#{height} dots"
            return 0
          end

          ctx.open_usb
          ctx.print_raster(data, width_dots: width, height_dots: height)
          0
        rescue Libptouch::Error => e
          warn e.message
          1
        ensure
          ctx&.dispose
        end
      end

      def usage_error(msg)
        warn msg
        warn "(try ptouch-print-png --help)"
        2
      end
    end
  end
end
