module Rspreadsheet

# this module contains methods used by several objects
module Tools
  using ClassExtensions if RUBY_VERSION > '2.1'
  
  def self.only_letters?(x); x.kind_of?(String) and x.match(/^[A-Za-z]*$/) != nil end
  def self.kind_of_integer?(x)
    (x.kind_of?(Numeric) and x.to_i==x) or 
    (x.kind_of?(String) and x.match(/^\d*(\.0*)?$/) != nil)
  end

  # converts cell adress like 'F12' to pair od integers [row,col]
  def self.convert_cell_address_to_coordinates(*addr)
    if addr.length == 1
      addr[0].to_s.match(/^([A-Za-z]{1,3})(\d{1,8})$/)
      colname = $~[1]
      rowi = $~[2].to_i
    elsif addr.length == 2
      a = addr[0]; b = addr[1]
      if a.kind_of?(Integer) and b.kind_of?(Integer) # most common case first
        colname,rowi = b,a 
      elsif only_letters?(a)
        if kind_of_integer?(b)
          colname,rowi = a,b.to_i
        else
          raise 'Wrong parameters - first is letters, but the seconds is not digits only'
        end
      elsif kind_of_integer?(a)
        if only_letters?(b)
          colname,rowi = b,a.to_i
        elsif kind_of_integer?(b)
          colname,rowi = b.to_i,a.to_i
        else
          raise 'Wrong second out of two paremeters - mix of digits and numbers'
        end
      else 
        raise 'Wrong first out of two paremeters - mix of digits and numbers'
      end
    else
      raise 'Wrong number of arguments'
    end
     
    return [rowi,convert_column_name_to_index(colname)]
  end
  
  def self.convert_column_name_to_index(colname)
    return colname if colname.kind_of?(Integer)
    ## first possibility how to implement it
    # colname=colname.rjust(3,'@')
    # return (colname[-1].ord-64)+(colname[-2].ord-64)*26+(colname[-3].ord-64)*26*26

    ## second possibility how to implement it
    # return (colname.to_i(36)-('A'*colname.size).to_i(36)).to_s(36).to_i(26)+('1'*colname.size).to_i(26)
    
    ## third possibility how to implement it (second one little shortened)
    s=colname.size
    return (colname.to_s.upcase.to_i(36)-(36**s-1).div(3.5)).to_s(36).to_i(26)+(26**s-1)/25
  end
  
  def self.convert_cell_coordinates_to_address(*coords)
    coords = coords[0] if coords.length == 1
    raise 'Wrong number of arguments' if coords.length != 2
    row = coords[0].to_i # security against string arguments
    col = coords[1].to_i
    colstring = ''
    if col > 702
      pom = (col-703).div(26*26)+1
      colstring += (pom+64).chr
      col -= pom*26*26
    end
    if col > 26
      pom = (col-27).div(26)+1
      colstring += (pom+64).chr
      col -= pom*26
    end
    colstring += (col+64).chr
    return colstring+row.to_s
  end
  def self.c2a(*x); convert_cell_coordinates_to_address(*x) end
  def self.a2c(*x); convert_cell_address_to_coordinates(*x) end
  # Finds {LibXML::XML::Namespace} object by its prefix. It knows all OpenDocument commonly used namespaces.
  # @return [LibXML::XML::Namespace] namespace object
  # @param prefix [String] namespace prefix
  def self.get_namespace(prefix)
    ns_array = {
      'office'=>"urn:oasis:names:tc:opendocument:xmlns:office:1.0",
      'style'=>"urn:oasis:names:tc:opendocument:xmlns:style:1.0",
      'text'=>"urn:oasis:names:tc:opendocument:xmlns:text:1.0",
      'table'=>"urn:oasis:names:tc:opendocument:xmlns:table:1.0",
      'draw'=>"urn:oasis:names:tc:opendocument:xmlns:drawing:1.0",
      'fo'=>"urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0",
      'xlink'=>"http://www.w3.org/1999/xlink",
      'dc'=>"http://purl.org/dc/elements/1.1/",
      'meta'=>"urn:oasis:names:tc:opendocument:xmlns:meta:1.0",
      'number'=>"urn:oasis:names:tc:opendocument:xmlns:datastyle:1.0",
      'presentation'=>"urn:oasis:names:tc:opendocument:xmlns:presentation:1.0",
      'svg'=>"urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0",
      'chart'=>"urn:oasis:names:tc:opendocument:xmlns:chart:1.0",
      'dr3d'=>"urn:oasis:names:tc:opendocument:xmlns:dr3d:1.0",
      'math'=>"http://www.w3.org/1998/Math/MathML",
      'form'=>"urn:oasis:names:tc:opendocument:xmlns:form:1.0",
      'script'=>"urn:oasis:names:tc:opendocument:xmlns:script:1.0",
      'ooo'=>"http://openoffice.org/2004/office",
      'ooow'=>"http://openoffice.org/2004/writer",
      'oooc'=>"http://openoffice.org/2004/calc",
      'dom'=>"http://www.w3.org/2001/xml-events",
      'xforms'=>"http://www.w3.org/2002/xforms",
      'xsd'=>"http://www.w3.org/2001/XMLSchema",
      'xsi'=>"http://www.w3.org/2001/XMLSchema-instance",
      'rpt'=>"http://openoffice.org/2005/report",
      'of'=>"urn:oasis:names:tc:opendocument:xmlns:of:1.2",
      'xhtml'=>"http://www.w3.org/1999/xhtml",
      'grddl'=>"http://www.w3.org/2003/g/data-view#",
      'tableooo'=>"http://openoffice.org/2009/table",
      'drawooo'=>"http://openoffice.org/2010/draw",
      'calcext'=>"urn:org:documentfoundation:names:experimental:calc:xmlns:calcext:1.0",
      'loext'=>"urn:org:documentfoundation:names:experimental:office:xmlns:loext:1.0",
      'field'=>"urn:openoffice:names:experimental:ooo-ms-interop:xmlns:field:1.0",
      'formx'=>"urn:openoffice:names:experimental:ooxml-odf-interop:xmlns:form:1.0",
      'css3t'=>"http://www.w3.org/TR/css3-text/",
      'manifest'=>"urn:oasis:names:tc:opendocument:xmlns:manifest:1.0"
    }
    if !defined?(@pomnode) or @pomnode.nil?
      @pomnode = LibXML::XML::Node.new('xxx')
    end
    if !defined?(@ns) or @ns.nil? then @ns={} end
    if @ns[prefix].nil?
      @ns[prefix] = LibXML::XML::Namespace.new(@pomnode, prefix, ns_array[prefix])
    end
    return @ns[prefix]
  end
  # sets namespaced attribute "ns_prefix:key" in node to value. if value == delete_value then remove the attribute
  def self.set_ns_attribute(node,ns_prefix,key,value,delete_value=nil)
    raise 'Tools.set_ns_attribute can not set attribute on nil node' if node.nil?
    ns = Tools.get_namespace(ns_prefix)
    attr = node.attributes.get_attribute_ns(ns.href, key)
    
    unless value==delete_value # set attribute
      if attr.nil? # create attribute if needed
        attr = LibXML::XML::Attr.new(node, key,'temporarilyempty')
        attr.namespaces.namespace = ns
      end
      attr.value = value.to_s
      attr
    else # remove attribute
      attr.remove! unless attr.nil? 
      nil
    end
  end
  def self.get_ns_attribute(node,ns_prefix,key,default=:undefined_default)
    if default==:undefined_default
      raise 'Nil does not have any attributes' if node.nil?
      node.attributes.get_attribute_ns(Tools.get_namespace(ns_prefix).href,key)
    else
      node.nil? ? default : node.attributes.get_attribute_ns(Tools.get_namespace(ns_prefix).href,key) || default
    end
  end
  def self.delete_ns_attribute(node,ns_prefix,key) 
    set_ns_attribute(node,ns_prefix,key,nil,nil)
  end
  def self.get_ns_attribute_value(node,ns_prefix,key,default=:undefined_default)
    if default==:undefined_default
      Tools.get_ns_attribute(node,ns_prefix,key).andand.value
    else
      node.nil? ? default : Tools.get_ns_attribute(node,ns_prefix,key,nil).andand.value || default
    end
  end
  def self.remove_ns_attribute(node,ns_prefix,key)
    ns = Tools.get_namespace(ns_prefix)
    attr = node.attributes.get_attribute_ns(ns.href, key)
    attr.remove! unless attr.nil? 
  end
  def self.prepare_ns_node(ns_prefix,nodename,value=nil)
    LibXML::XML::Node.new(nodename,value, Tools.get_namespace(ns_prefix))
  end
  def self.insert_as_first_node_child(node,subnode)
    if node.first?
      node.first.prev = subnode
    else
      node << subnode
    end
  end
  
  def self.get_unused_filename(zip,prefix, extension)
    (1000..9999).each do |ndx|
      filename = prefix + ndx.to_s + ((Time.now.to_r*1000000000).to_i.to_s(16)) + extension
      return filename if zip.find_entry(filename).nil?
    end
    raise 'Could not get unused filename within sane times of iterations'
  end
 
  def self.new_time_value(h,m,s)
    Time.new(StartOfEpoch.year,StartOfEpoch.month,StartOfEpoch.day,h,m,s)
  end
  
  def self.output_to_zip_stream(io,&block)
    if io.kind_of? File or io.kind_of? String
      Zip::File.open(io, 'br+') do |zip|
        yield zip
      end
    elsif io.kind_of? StringIO # or io.kind_of? IO
      Zip::OutputStream.write_buffer(io) do |zip|
        yield zip
      end
    end
  end
 
  def self.content_xml_diff(filename1,filename2)
    content_xml1 = Zip::File.open(filename1) do |zip|
      LibXML::XML::Document.io zip.get_input_stream('content.xml')
    end
    content_xml2 = Zip::File.open(filename2) do |zip|
      LibXML::XML::Document.io zip.get_input_stream('content.xml')
    end
    
    return xml_diff(content_xml1.root,content_xml2.root)
  end
  
  def self.xml_file_diff(filename1,filename2)
    content_xml1 = LibXML::XML::Document.file(filename1).root
    content_xml2 = LibXML::XML::Document.file(filename2).root
    return xml_diff(content_xml1, content_xml2)
  end

  def self.xml_diff(xml_node1,xml_node2)
    message = []
    message << xml_node2.first_diff(xml_node1)
    message << xml_node1.first_diff(xml_node2)
    message << 'content XML not equal' unless xml_node1.to_s.should == xml_node2.to_s
    message = message.compact.join('; ')
    message = nil if message == ''
    message
  end
end
  
end
