module MARC

    # Provides methods for serializing and deserializing MARC::Record
    # objects as MARC21 in transmission format.

    class MARC21

        LEADER_LENGTH = 24
        DIRECTORY_ENTRY_LENGTH = 12
        SUBFIELD_INDICATOR = 0x1F.chr
        END_OF_FIELD = 0x1E.chr
        END_OF_RECORD = 0x1D.chr


        # Returns the MARC21 serialization for a MARC::Record 

        def encode(record)
            directory = ''
            fields = ''
            offset = 0
            for field in record.fields

                # encode the field
                field_data = ''
                if field.class == MARC::Field
                    field_data = field.indicator1 + field.indicator2 
                    for s in field.subfields
                        field_data += SUBFIELD_INDICATOR + s.code + s.value
                    end
                elsif field.class == MARC::Control
                    field_data = field.value
                end
                field_data += END_OF_FIELD

                # calculate directory entry for the field
                field_length = field_data.length()
                directory += sprintf("%03s%04i%05i", field.tag, field_length, 
                    offset)

                # add field to data for other fields
                fields += field_data 

                # update offset for next field
                offset += field_length
            end

            # determine the base (leader + directory)
            base = record.leader + directory + END_OF_FIELD

            # determine complete record
            marc = base + fields + END_OF_RECORD

            # update leader with the byte offest to the end of the directory
            marc[12..16] = sprintf("%05i", base.length())

            # update the record length
            marc[0..4] = sprintf("%05i", marc.length())
            
            # store updated leader in the record that was passed in
            record.leader = marc[0..LEADER_LENGTH]

            # return encoded marc
            return marc 
        end


        # Deserializes MARC21 as a MARC::Record object

        def decode(marc)
            record = Record.new()
            record.leader = marc[0..LEADER_LENGTH]

            # where the field data starts
            base_address = record.leader[12..16].to_i

            # get the byte offsets from the record directory
            directory = marc[LEADER_LENGTH..base_address-1]

            # the number of fields in the record corresponds to 
            # how many directory entries there are
            num_fields = directory.length / DIRECTORY_ENTRY_LENGTH

            0.upto(num_fields-1) do |field_num|

                # pull the directory entry for a field out
                entry_start = field_num * DIRECTORY_ENTRY_LENGTH
                entry_end = entry_start + DIRECTORY_ENTRY_LENGTH
                entry = directory[entry_start..entry_end]
                
                # extract the tag, length and offset for pulling the
                # field out of the field portion
                tag = entry[0..2]
                length = entry[3..6].to_i
                offset = entry[7..11].to_i
                field_start = base_address + offset
                field_end = field_start + length - 1
                field_data = marc[field_start..field_end]

                # remove end of field
                field_data.delete!(END_OF_FIELD)
               
                # add a control field or variable field
                if tag < '010'
                    record.append(MARC::Control.new(tag,field_data))
                else
                    field = MARC::Field.new(tag)

                    # get all subfields
                    subfields = field_data.split(SUBFIELD_INDICATOR)

                    # get indicators
                    indicators = subfields.shift()
                    field.indicator1 = indicators[0,1]
                    field.indicator2 = indicators[1,1]

                    # add each subfield to the field
                    subfields.each() do |data|
                        subfield = MARC::Subfield.new(data[0,1],data[1..-1])
                        field.append(subfield)
                    end

                    # add the field to the record
                    record.append(field)
                end
            end

            return record
        end

    end

end