#!/usr/bin/ruby

# Copyright (C) 2023-2024, Johannes Graßler <info@computer-grassler.de>.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
#   Redistributions of source code must retain the above copyright notice, this
#   list of conditions and the following disclaimer.
#
#   Redistributions in binary form must reproduce the above copyright notice,
#   this list of conditions and the following disclaimer in the documentation
#   and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.

require 'csv'
require 'nokogiri'
require 'optparse'
require 'tempfile'


# ZugFeRD, CrossIndustryInvoice flavoured
ZIGLOBAL = { 'invoice_id' => '//rsm:ExchangedDocument/ram:ID',
             'make_0' => '//ram:SellerTradeParty/ram:Name',
             'shippingdate' => '//ram:ActualDeliverySupplyChainEvent/ram:OccurrenceDateTime/udt:DateTimeString' }

ZIITEM = { 'description' => './ram:SpecifiedTradeProduct/ram:Name',
            'lastcost' => './ram:SpecifiedLineTradeAgreement/ram:NetPriceProductTradePrice/ram:ChargeAmount',
            'lastcost_0' => './ram:SpecifiedLineTradeAgreement/ram:NetPriceProductTradePrice/ram:ChargeAmount',
            'sellprice' => './ram:SpecifiedLineTradeAgreement/ram:NetPriceProductTradePrice/ram:ChargeAmount',
            'model_0' => './ram:SpecifiedTradeProduct/ram:SellerAssignedID',
            'partnumber' => './ram:SpecifiedTradeProduct/ram:SellerAssignedID',
            'qty' => './ram:SpecifiedLineTradeDelivery/ram:BilledQuantity' }

# ZugFeRD, CrossIndustryDocument flavoured (still needs to be implemented)

# XRechnung
XGLOBAL = { 'invoice_id' => '//cbc:ID',
            'make_0' => '//cac:AccountingSupplierParty/cac:Party/cac:PartyName/cbc:Name',
            'shippingdate' => '//cac:Delivery/cbc:ActualDeliveryDate'}

XITEM = { 'description' => './cac:Item/cbc:Description',
            'lastcost' => './cac:Price/cbc:PriceAmount',
            'lastcost_0' => './cac:Price/cbc:PriceAmount',
            'sellprice' => './cac:Price/cbc:PriceAmount',
            'model_0' => './cac:Item/cac:SellersItemIdentification/cbc:ID',
            'partnumber' => './cac:Item/cac:SellersItemIdentification/cbc:ID',
            'qty' => './cbc:InvoicedQuantity' }

XITEMS_XPATH='//cac:InvoiceLine'
ZIITEMS_XPATH='//ram:IncludedSupplyChainTradeLineItem'

clifields = {
  'bin_id' => nil,
  'make_0' => nil,
  'warehouse_id' => nil
}

OptionParser.new do |opts|
  opts.banner = "Usage: " + $0 + "[options]"

  opts.on("-b", "--bin-id=ID", "Default bin ID (in warehouse) for item") do |o|
    clifields['bin_id'] = o
  end

  opts.on("-m", "--make=MAKE", "make_0 (vendor ID) for item") do |o|
    clifields['make_0'] = o
  end

  opts.on("-w", "--warehouse-id=ID", "Default warehouse ID for item") do |o|
    clifields['warehouse_id'] = o
  end
end.parse!

template_row = Hash.new()
rows = []

header_row = (XGLOBAL.keys + XITEM.keys + clifields.keys).uniq

# initialize row template

header_row.each { |k|
  template_row[k] = ""
}

csv = CSV.new($stdout)
csv << header_row

ARGV.each { |arg|
  doc = nil

  if arg.match?(/xml$/i)
    doc = Nokogiri::XML::Document.parse(File.open(arg))
  elsif arg.match?(/pdf$/i)
    tmpfile = Tempfile.new(['zugferd', '.xml'])
    tmpfile.close() # We don't need this open
    system("pdfdetach", "-save", "1", "-o", tmpfile.path, arg)
    doc = Nokogiri::XML::Document.parse(File.open(tmpfile.path))
  end

  doctype = doc.root.node_name()

  xpath_global = nil
  xpath_items = nil
  items_xpath = nil

  case doctype
    when 'CrossIndustryInvoice'
      xpath_global = ZIGLOBAL
      xpath_items = ZIITEM
      items_xpath = ZIITEMS_XPATH
    when 'Invoice'
      xpath_global = XGLOBAL
      xpath_items = XITEM
      items_xpath = XITEMS_XPATH
  end

  xpath_global.keys.each { |k|
    if clifields.key?(k)
      template_row[k] = clifields[k]
    else
      template_row[k] = doc.xpath(xpath_global[k])[0].content
    end
  }


  doc.xpath(items_xpath).each { |item|
    item_hash = Hash.new.merge(template_row)
    item_array = []
    xpath_items.keys.each { |k|
      content = item.xpath(xpath_items[k])[0].content
      content.gsub!('SS', 'ss')
      content.gsub!(/[^[:print:]]/, '')
      if clifields.key?(k)
        item_hash[k] = clifields[k]
      else
        item_hash[k] = content
      end
    }
    item_hash.keys.each {|k|
      item_array.push(item_hash[k])
    }
    csv << item_array
  }
}
