#!/usr/bin/python

# tz_s19.py
#       --copyright--                   Copyright 2007 (C) Tranzoa, Co. All rights reserved.    Warranty: You're free and on your own here. This code is not necessarily up-to-date or of public quality.
#       --url--                         http://www.tranzoa.net/tzpython/
#       --email--                       pycode is the name to send to. tranzoa.com is the place to send to.
#       --bodstamps--
#       August 29, 2007         bar
#       August 31, 2007         bar     fix an error printout
#                                       base_adr
#       September 2, 2007       bar     comment
#       November 18, 2007       bar     turn on doxygen
#       November 27, 2007       bar     insert boilerplate copyright
#       November 30, 2007       bar     remove redundant, extra, duplicated, doubled import
#       May 17, 2008            bar     email adr
#       November 29, 2011       bar     pyflake cleanup
#       May 27, 2012            bar     doxygen namespace
#       --eodstamps--
##      \file
#       \namespace              tzpython.tz_s19
#
#
#       Motorola S19 file handling.
#
#
#       This logic is minimal stuff.
#       It only parses 32-bit addressed (S3) data records.
#       It stores up S0 (name) and S7 (32 bit starting address) records dumbly.
#       It doesn't handle the 16-bit and 24-bit records.
#       It expects all the data to be contiguous, starting from an arbitrary address.
#       Many descriptions of S records are out there on the net.
#           e.g. http://www.amelek.gda.pl/avr/uisp/srecord.htm
#                which says it's a copy of a man page.
#
#       As a further note, I've found at least one program that
#         wants the S0 record at the top of the file and the S7
#         record at the bottom.
#
#


from    types                   import ListType, TupleType



def checksum(line) :
    """
        From: http://www.myelin.co.nz/post/2005/12/5/
    """

    length  = int(line[2:4], 16)
    bytes   = [ int(line[i * 2 : i * 2 + 2], 16) for i in range(1, length + 1) ]

    return(~sum(bytes) & 0xff)



def data_record(adr, s) :
    """
        Create an S19 data record (line of text complete with checksum) from the given string or list/tuple,
        which must be 0x1c bytes long or shorter.
        If the input data is zero length, return "".
    """

    if  isinstance(s, ListType) or isinstance(s, TupleType) :
        s   = "".join([ chr(c) for c in s ])

    if  len(s) > 0x1c :
        raise "Too long data record: ", str(len(s))

    if  not s :
        return("")

    bs  = [ "%02X" % ( ord(b) ) for b in s ]
    bs  = "".join(bs)

    li  = "S3%02X%08X%s%02X" % ( len(s) + 5, adr, bs, 0 )

    return( li[:-2] + "%02X" % ( checksum(li) ) )




def read_file(file_name) :
    """
        Read an S19 file (named or opened) in to a returned array.
        Also, return any non-data lines read verbatim from the file, in order.
    """

    if  isinstance(file_name, basestring) :
        fi          = open(file_name, "r")
    else :
        fi          = file_name
        file_name   = "string"

    ln              = 0

    base_adr        = -1
    program         = ""
    xlines          = []

    while True :
        li  = fi.readline()
        if  not li :
            break

        ln += 1

        li  = li.strip()

        bc  = int(li[2:4], 16)

        bs  = [ int(li[i * 2 : i * 2 + 2], 16) for i in range(1, bc + 1) ]

        cs  = ~sum(bs) & 0xff
        lcs = int(li[-2:], 16)
        if  cs != lcs :
            raise "Checksum error on line %u %0x2x != %02x in file [%s]!" % ( ln, cs, lcs, file_name )

        if  li[1] == '3' :
            bc -= 5                 # drop the byte count and address
            bs  = bs[5:]

            adr = (int(li[4:6], 16) << 24) + (int(li[6:8], 16) << 16) + (int(li[8:10], 16) << 8) + int(li[10:12], 16)

            if  base_adr    < 0 :
                base_adr    = adr;

            if  adr != base_adr + len(program) :
                print "%08x %08x %08x" % ( adr, base_adr, base_adr + len(program) )
                raise "Out of order data record on line %u of %s!" % ( ln, file_name )

            program += "".join([ chr(c) for c in bs ])

        else :

            xlines.append(li)

        pass

    return( ( program, base_adr, xlines ) )


#
#
#
if __name__ == '__main__' :

    import  sys

    import  TZCommandLineAtFile
    import  replace_file


    del(sys.argv[0])

    TZCommandLineAtFile.expand_at_sign_command_line_files(sys.argv)

    if  len(sys.argv) != 2 :
        print "Tell me an input .S19 file and an output binary file name!"
        sys.exit(101)


    ifile_name  = sys.argv.pop(0)
    ofile_name  = sys.argv.pop(0)

    ( program, base_adr, xlines ) = read_file(ifile_name)

    tfn = ofile_name + ".tmp"

    fo  = open(tfn, "w")

    for li in xlines[0:-1] :                        # put out all extra lines read from the input file but the last one
        print >> fo, li

    for i in xrange(0, len(program), 0x1c) :
        print >> fo, data_record(i, program[i : i + 0x1c])

    for li in xlines[-1:] :                         # put out the last line - copied from the last input file line
        print >> fo, li

    fo.close()

    replace_file.replace_file(ofile_name, tfn, ofile_name + ".bak")


#
#
#
# eof
