#!/usr/bin/env ruby

require 'bundler/inline'

#gemfile do
#  source 'https://rubygems.org'
#  gem 'zip'
#  gem 'nokogiri'
#  gem 'tty-prompt'
#  gem 'debug'
#end


require 'zip'
require 'nokogiri'
require 'tty-prompt'
require 'debug'


class Epub
  def initialize(file)
    @file = file
  end

  def read_epub
    Zip::File.open(@file) do |zip_file|
      opf_name = find_opf_file(zip_file)
      opf_entry = zip_file.find_entry(opf_name)
      opf_content = opf_entry.get_input_stream.read
      opf_doc = Nokogiri::XML(opf_content)

      yield zip_file, opf_doc, opf_name if block_given?
    end
  end

  def write_epub
    read_epub do |zip_file, opf_doc, opf_name|
      yield zip_file, opf_doc, opf_name if block_given?
      zip_file.get_output_stream(opf_name) do |output_stream|
        output_stream.write(opf_doc.to_xml)
      end
    end
  end

  def opf
    read_epub do |zip_file, opf_doc, opf_name|
      opf_doc
    end
  end

  def series
    read_epub do |zip_file, opf_doc, opf_name|
      series_element = opf_doc.at_css('meta[name="calibre:series"]')
      series_element["content"] if series_element 
    end
  end

  def series=(new_series_name)
    write_epub do |zip_file, opf_doc, opf_name|
      series_element = opf_doc.at_css('meta[name="calibre:series"]')

      if series_element && new_series_name.nil?
        # Delete the <meta> element for the series
        series_element.remove
      elsif series_element
        # Update the content of the <meta> element
        series_element['content'] = new_series_name
      else
        # Create a new <meta> element for the series if it doesn't exist
        opf_namespace_url = "http://www.idpf.org/2007/opf"
        metadata_element = opf_doc.at_css("metadata, opf|metadata", 'opf' => opf_namespace_url)
        series_element = Nokogiri::XML::Node.new('meta', opf_doc)
        series_element['name'] = 'calibre:series'
        series_element['content'] = new_series_name
        metadata_element.add_child(series_element)
      end
    end
  end

  def series_index
    read_epub do |zip_file, opf_doc, opf_name|
      series_index = opf_doc.at_css('meta[name="calibre:series_index"]')
      series_index['content'] if series_index
    end
  end

  def series_index=(new_si)
    write_epub do |zip_file, opf_doc, opf_name|
      series_index = opf_doc.at_css('meta[name="calibre:series_index"]')

      old_si = series_index['content'] if series_index

      if series_index && new_si.nil?
        series_index.remove 
      elsif series_index
        series_index['content'] = new_si
      else
        opf_namespace_url = "http://www.idpf.org/2007/opf"
        metadata_element = opf_doc.at_css("metadata, opf|metadata", 'opf' => opf_namespace_url)

        si_element = Nokogiri::XML::Node.new('meta', opf_doc)
        si_element['name'] = 'calibre:series_index'
        si_element['content'] = new_si
        metadata_element.add_child(si_element)
      end
    end
  end

  def find_opf_file(zip_file)
    # Locate and read the container.xml file
    container_xml_entry = zip_file.find_entry('META-INF/container.xml')
    container_xml = container_xml_entry.get_input_stream.read

    # Parse the container.xml file
    doc = Nokogiri::XML(container_xml)

    # Find the rootfile element and extract the 'full-path' attribute
    doc.at_xpath("xmlns:container/xmlns:rootfiles/xmlns:rootfile")['full-path']
  end
end

def run(argv)
  files = []
  action = nil
  attribute = nil
  new_series_name = nil

  argv.each_with_index do |arg, index|
    if arg == 'get' || arg == 'set'
      action = arg.to_sym
      attribute = argv[index + 1]
      new_series_name = argv[index + 2]
      break
    else
      files << arg if File.exist?(arg)
    end
  end

  raise ArgumentError.new("Action must be :get or :set") unless [:get, :set].include?(action)
  raise ArgumentError.new("Attribute must be 'series' or 'series_index'") unless ['series', 'series_index'].include?(attribute)
  
  files.sort!

  if attribute == 'series'
    result = files.map do |file|
      e = Epub.new(file)
      {file: }.merge  e.series_name(file, action:, new_series_name:)
    end

    result.each { |r| puts "#{r[:file]} : #{r[:series_name]}" } if action == :get
    result.each { |r| puts "#{r[:file]} : #{r[:old_series_name]} => #{r[:new_series_name]}" } if action == :set

  elsif attribute == 'series_index'
    result = files.map do |file|
      e = Epub.new(file)
      {file: }.merge(e.series_index(file, action: ))
    end

    result.each { |r| puts "#{r[:file]} : #{r[:old_series_index]}" } if action == :get
    result.each { |r| puts "#{r[:file]} : #{r[:old_series_index]} -> #{r[:new_series_index]}" } if action == :set
  end
end

if __FILE__ == $PROGRAM_NAME
  run(ARGV)
end
