require 'fix/protocol/message_part'
require 'fix/protocol/unordered_part'
require 'fix/protocol/repeating_message_part'

module Fix
  module Protocol

    #
    # Represents an instance of a FIX message
    #
    class Message < MessagePart

      # Default version for when we do not specify anything
      DEFAULT_VERSION = 'FIX.4.2'
      @@expected_version = DEFAULT_VERSION

      #
      # Allows the version tag to be overridden at runtime
      #
      # @param version [String] The version to output and expect in messages
      #
      def self.version=(v)
        @@expected_version = v
      end

      part :header do
        field :version,     tag: 8, required: true, default: @@expected_version
        field :body_length, tag: 9

        unordered :header_fields do
          field :msg_type,        tag: 35,  required: true
          field :sender_comp_id,  tag: 49,  required: true
          field :target_comp_id,  tag: 56,  required: true
          field :msg_seq_num,     tag: 34,  required: true, type: :integer
          field :sending_time,    tag: 52,  required: true, type: :timestamp, default: proc { Time.now }
          field :sender_sub_id,   tag: 50
          field :target_sub_id,   tag: 57
          field :obo_comp_id,     tag: 115
          field :dt_comp_id,      tag: 128
        end
      end

      def initialize
        super
        header.msg_type = MessageClassMapping.reverse_get(self.class)
      end

      #
      # Dumps this message as a FIX protocol message, it will automatically
      # calculate the body length and and checksum
      #
      # @return [String] The FIX message
      #
      def dump
        if valid?
          dumped = super
          header.body_length = dumped.gsub(/^8=[^\x01]+\x01/, '').gsub(/^9=[^\x01]+\x01/, '').length
          dumped = super
          "#{dumped}10=#{'%03d' % (dumped.bytes.inject(&:+) % 256)}\x01"
        end
      end

      #
      # Whether this instance is ready to be dumped as a valid FIX message
      #
      # @return [Boolean] Whether there are errors present for this instance
      #
      def valid?
        (errors.nil? || errors.empty?) && parse_failure.nil?
      end

      #
      # Returns the errors relevant to the message header
      #
      # @return [Array<String>] The errors on the message header
      #
      def errors
        if (version == @@expected_version)
          super
        else
          [super, "Unsupported version: <#{version}>, expected <#{@@expected_version}>"].flatten
        end
      end

    end
  end
end
