# -*- coding: utf-8 -*-
###############################################################################
#
# Properties - A module for creating Excel property sets.
#
#
# Used in conjunction with WriteExcel
#
# Copyright 2000-2010, John McNamara.
#
# original written in Perl by John McNamara
# converted to Ruby by Hideo Nakamura, cxn03651@msj.biglobe.ne.jp
#

require 'date'

###############################################################################
#
# create_summary_property_set().
#
# Create the SummaryInformation property set. This is mainly used for the
# Title, Subject, Author, Keywords, Comments, Last author keywords and the
# creation date.
#
def create_summary_property_set(properties)       #:nodoc:
    byte_order          = [0xFFFE].pack('v')
    version             = [0x0000].pack('v')
    system_id           = [0x00020105].pack('V')
    class_id            = ['00000000000000000000000000000000'].pack('H*')
    num_property_sets   = [0x0001].pack('V')
    format_id           = ['E0859FF2F94F6810AB9108002B27B3D9'].pack('H*')
    offset              = [0x0030].pack('V')
    num_property        = [properties.size].pack('V')
    property_offsets    = ''

    # Create the property set data block and calculate the offsets into it.
    property_data, offsets = pack_property_data(properties)

    # Create the property type and offsets based on the previous calculation.
    0.upto(properties.size - 1) do |i|
      property_offsets += [properties[i][0], offsets[i]].pack('VV')
    end

    # Size of size (4 bytes) +  num_property (4 bytes) + the data structures.
    size = 8 + (property_offsets).bytesize + property_data.bytesize
    size = [size].pack('V')

    byte_order         +
    version            +
    system_id          +
    class_id           +
    num_property_sets  +
    format_id          +
    offset             +
    size               +
    num_property       +
    property_offsets   +
    property_data
end


###############################################################################
#
# Create the DocSummaryInformation property set. This is mainly used for the
# Manager, Company and Category keywords.
#
# The DocSummary also contains a stream for user defined properties. However
# this is a little arcane and probably not worth the implementation effort.
#
def create_doc_summary_property_set(properties)       #:nodoc:
    byte_order          = [0xFFFE].pack('v')
    version             = [0x0000].pack('v')
    system_id           = [0x00020105].pack('V')
    class_id            = ['00000000000000000000000000000000'].pack('H*')
    num_property_sets   = [0x0002].pack('V')

    format_id_0         = ['02D5CDD59C2E1B10939708002B2CF9AE'].pack('H*')
    format_id_1         = ['05D5CDD59C2E1B10939708002B2CF9AE'].pack('H*')
    offset_0            = [0x0044].pack('V')
    num_property_0      = [properties.size].pack('V')
    property_offsets_0  = ''

    # Create the property set data block and calculate the offsets into it.
    property_data_0, offsets = pack_property_data(properties)

    # Create the property type and offsets based on the previous calculation.
    0.upto(properties.size-1) do |i|
      property_offsets_0 += [properties[i][0], offsets[i]].pack('VV')
    end

    # Size of size (4 bytes) +  num_property (4 bytes) + the data structures.
    data_len = 8 + (property_offsets_0).bytesize + property_data_0.bytesize
    size_0   = [data_len].pack('V')

    # The second property set offset is at the end of the first property set.
    offset_1 = [0x0044 + data_len].pack('V')

    # We will use a static property set stream rather than try to generate it.
    property_data_1 = [%w(
        98 00 00 00 03 00 00 00 00 00 00 00 20 00 00 00
        01 00 00 00 36 00 00 00 02 00 00 00 3E 00 00 00
        01 00 00 00 02 00 00 00 0A 00 00 00 5F 50 49 44
        5F 47 55 49 44 00 02 00 00 00 E4 04 00 00 41 00
        00 00 4E 00 00 00 7B 00 31 00 36 00 43 00 34 00
        42 00 38 00 33 00 42 00 2D 00 39 00 36 00 35 00
        46 00 2D 00 34 00 42 00 32 00 31 00 2D 00 39 00
        30 00 33 00 44 00 2D 00 39 00 31 00 30 00 46 00
        41 00 44 00 46 00 41 00 37 00 30 00 31 00 42 00
        7D 00 00 00 00 00 00 00 2D 00 39 00 30 00 33 00
    ).join('')].pack('H*')

    byte_order         +
    version            +
    system_id          +
    class_id           +
    num_property_sets  +
    format_id_0        +
    offset_0           +
    format_id_1        +
    offset_1           +
    size_0             +
    num_property_0     +
    property_offsets_0 +
    property_data_0    +
    property_data_1
end


###############################################################################
#
# _pack_property_data().
#
# Create a packed property set structure. Strings are null terminated and
# padded to a 4 byte boundary. We also use this function to keep track of the
# property offsets within the data structure. These offsets are used by the
# calling functions. Currently we only need to handle 4 property types:
# VT_I2, VT_LPSTR, VT_FILETIME.
#
def pack_property_data(properties, offset = 0)       #:nodoc:
    packed_property     = ''
    data                = ''
    offsets             = []

    # Get the strings codepage from the first property.
    codepage = properties[0][2]

    # The properties start after 8 bytes for size + num_properties + 8 bytes
    # for each propety type/offset pair.
    offset += 8 * (properties.size + 1)

    properties.each do |property|
      offsets.push(offset)

      property_type = property[1]

      if    property_type == 'VT_I2'
        packed_property = pack_VT_I2(property[2])
      elsif property_type == 'VT_LPSTR'
        packed_property = pack_VT_LPSTR(property[2], codepage)
      elsif property_type == 'VT_FILETIME'
        packed_property = pack_VT_FILETIME(property[2])
      else
        raise "Unknown property type: '#{property_type}'\n"
      end

      offset += packed_property.bytesize
      data   += packed_property
    end

    [data, offsets]
end

###############################################################################
#
# _pack_VT_I2().
#
# Pack an OLE property type: VT_I2, 16-bit signed integer.
#
def pack_VT_I2(value)       #:nodoc:
    type    = 0x0002
    [type, value].pack('VV')
end

###############################################################################
#
# _pack_VT_LPSTR().
#
# Pack an OLE property type: VT_LPSTR, String in the Codepage encoding.
# The strings are null terminated and padded to a 4 byte boundary.
#
def pack_VT_LPSTR(str, codepage)       #:nodoc:
    type        = 0x001E
    string      =
      ruby_18 { "#{str}\0" } ||
      ruby_19 { str.force_encoding('BINARY') + "\0".encode('BINARY') }

    if codepage == 0x04E4
      # Latin1
      length = string.bytesize
    elsif codepage == 0xFDE9
      # utf8
      length = string.bytesize
    else
      raise "Unknown codepage: #{codepage}\n"
    end

    # Pack the data.
    data  = [type, length].pack('VV')
    data += string

    # The packed data has to null padded to a 4 byte boundary.
    if (extra = length % 4) != 0
        data += "\0" * (4 - extra)
    end
    data
end

###############################################################################
#
# _pack_VT_FILETIME().
#
# Pack an OLE property type: VT_FILETIME.
#
def pack_VT_FILETIME(localtime)       #:nodoc:
  type        = 0x0040

  epoch = DateTime.new(1601, 1, 1)

  t = localtime.getgm

  datetime = DateTime.new(
                t.year,
                t.mon,
                t.mday,
                t.hour,
                t.min,
                t.sec,
                t.usec
              )
  bignum = (datetime - epoch) * 86400 * 1e7.to_i
  high, low = bignum.divmod 1 << 32

  [type].pack('V') + [low, high].pack('V2')
end
