-- res.e
-- support routine for res files.

include get.e
include file.e
include image.e



-----------------------------------------------------------------------------
-- read mode: where is the data from?
constant       
    RES_NORMAL      = 1,    -- read from non-resource files
    RES_BUNDLED     = 2,    -- read from a resource file
    RES_EXE         = 3     -- read from an exe file
    
-- mode
integer resMode
    

-----------------------------------------------------------------------------
-- hold resource file data
integer
    resourceStart      -- offset into resource file; used when it's an EXE
    
sequence
    resourceFile,        -- name of resource file
    
    -- resource file directory
    resName,        -- file
    resSize,        -- size
    resOffset,      -- offset       

    -- open file handles (max of 100)    
    handleMax,      -- offset+size-1
    handleOffset    -- offset into file

    
    resourceStart  = 0
    resourceFile    = {}        -- no name
        
    resName = {}
    resSize = {}
    resOffset = {}
    
    handleMax       = repeat( 0, 100 )
    handleOffset    = repeat( 0, 100 )



-----------------------------------------------------------------------------
function readNum( integer handle )    

    -- read a \n delimited text number from a file    
    sequence s
    
    s = gets( handle )
    s = value( s )
    if s[1] != GET_SUCCESS then
        return 0
    else
        return s[2]
    end if         
    
end function


-----------------------------------------------------------------------------
function readString( integer handle )    

    -- read string from file, remove ending line feed
    sequence s
    s = gets( handle )
    return s[1..length(s)-1]
    
end function


-----------------------------------------------------------------------------
procedure readResFile( sequence s )

    -- reads a resource file, and fills in the resDir values
    integer count, handle, here, ignore
    
    -- open file
    handle = open( s, "rb" )

    -- move to beginning
    ignore = seek( handle, resourceStart )

    -- get the number of entries in the resource file
    count = readNum( handle )

    for i = 1 to count do
        
        -- read the file name
        resName = append( resName, readString( handle ) )

        -- get the length
        resSize = append( resSize, readNum( handle ) )

        -- file offset
        resOffset = append( resOffset, where( handle ) )

        -- move ahead
        here = where( handle )
        if seek( handle, here + resSize[i] ) then
            puts( 1, "Error: End of Resource File!\n" )
            abort(0)
        end if
                
    end for

end procedure


-----------------------------------------------------------------------------
function scanBackwards( integer handle )
    -- scan file backwards, accumulating text
    -- stop at '\n'
    integer at, char
    sequence s

    -- clear string
    s = {}

    -- get position
    at = where( handle )

    while 1 do  
        
        -- get a byte
        char = getc( handle )

        -- end of string?
        if char = '\n' then
            exit
        end if
        
        -- attach        
        s = prepend( s, char ) 

        -- move backwards
        at = at - 1

        -- attempt to seek        
        if seek( handle, at ) then
            -- error!
            return ""
        end if
        
    end while           

    return s    
end function

-----------------------------------------------------------------------------
function getFileSize( sequence fName )
    -- return length of a file
    object list
    
    -- get file information
    list = dir( fName )
    
    -- doesn't exist
    if compare( list, -1 ) = 0 then
        return 0
    end if
    
    -- return length
    return list[1][D_SIZE]

end function



constant EXT = { "", ".EX", ".EXW", ".E", ".EW" }
-----------------------------------------------------------------------------
procedure setResMode()
    -- set the mode of the resource file reading
    integer at, handle, ignore
    sequence args, fName, text

    -- assume normal mode
    resMode = RES_NORMAL

    -- read the command line
    args = command_line()

    -- get the name
    fName = args[2]

    -- did they remember an extention?
    for i = 1 to length( EXT ) do
        if compare( dir( fName & EXT[i] ), -1 ) != 0 then
            fName = fName & EXT[i]
            exit
        end if
    end for

    -- open the file
    handle = open( fName, "rb" )
    
    -- error?
    if handle = -1 then
        puts( 1, "Unable to determine executable's extention!\n" )
        abort(0)
    end if

    -- look on the end of the file
    at = getFileSize( fName ) - 1
    ignore = seek( handle, at )

    -- read tag
    text = scanBackwards( handle )

    if compare( text, "RES" ) = 0 then
    
        -- it's in the EXE file. skip "\nres"
        at = getFileSize( fName ) - 5        
        ignore = seek( handle, at )        
        text = scanBackwards( handle )
    
        -- should be the size
        text = value( text )
        if text[1] = GET_SUCCESS then
        
            -- save offset
            resourceStart = text[2]
            
            -- save mode
            resMode = RES_EXE
            
            -- save file name
            resourceFile = fName
        
            -- close the file
            close( handle )

            -- read the file data
            readResFile( fName )
            
            -- leave routine
            return          
            
        end if         
        

    end if
    
    close( handle )
    
    -- extension?
    at = find( '.', fName )    
    if at = 0 then
        return
    end if
    
    -- change extention
    fName = fName[1..at] & "res"

    -- look for the file
    if compare( dir( fName ), -1 ) != 0 then
        -- found
        resMode = RES_BUNDLED

        -- save name
        resourceFile = fName
        
        -- read the file data
        readResFile( fName )
        
    end if

    
end procedure


-----------------------------------------------------------------------------
global function rOpen( sequence res, sequence mode )

    -- open a resource file
    integer handle, at

    -- open a resource file
    if resMode = RES_NORMAL then

        -- read the file normally
        handle = open( res, mode )
        
        -- check for error
        if handle = -1 then
            -- return error
            return -1
        end if
        
        -- get the file size
        handleMax[handle] = getFileSize( res ) - 1
        
        -- get the offset
        handleOffset[handle] = 0


    else
    
        -- find it in the resource file
        at = find( res, resName )
        if at = 0 then
            -- error
            printf( 1, "Error: Unable to find resource file %s\n", {res} )
            abort(0)
        end if
        
        -- open the resource file instead
        handle = open( resourceFile, mode )
        if handle = -1 then
            -- return error
            return -1
        end if

        -- get the offset
        handleOffset[handle] = resOffset[at]

        -- get the end of file location
        handleMax[handle] = handleOffset[handle] + resSize[at] - 1
        
        -- position correctly
        if seek( handle, handleOffset[handle] ) then
            -- error
            puts( 1, "Error: Resource file is corrupted.\n" )
            abort(0)
        end if
        
    end if

    return handle
        
end function




-----------------------------------------------------------------------------
global function rEOF( integer handle )

    -- true if past end of file
    
    -- test
    return where( handle ) > handleMax[handle]
                    
end function


-----------------------------------------------------------------------------
global function rSeek( integer handle, integer at )
    -- seek relative position

    -- before file?    
    if at < 0 then         
        -- before beginning of file
        return -1        
    end if
    
    -- add offset
    at = at + handleOffset[handle]
    
    -- check end of file
    if at >= handleMax[handle] then
        -- past end of file
        return -1
    end if
    
    return seek( handle, at )
    
end function


-----------------------------------------------------------------------------
global function rWhere( integer handle )

    -- return relative position
    return where(handle) - handleOffset[handle]
    
end function


-----------------------------------------------------------------------------
global procedure rFile( sequence fName )
    -- dummy function, needed by MAKERES
    
    -- prevent complaint by compiler
    fName = fName
end procedure


-----------------------------------------------------------------------------
-- set resource file read mode
setResMode()



-----------------------------------------------------------------------------
-----------------------------------------------------------------------------
-- Read bitmaps from a resource file
-- taken from Euphoria's image.e file
-- changes:
--
-- read_bitmaps renamed to rReadBmp
-- open() function in rReadBmp() changed to rOpen
-- removed unused routines

constant BMPFILEHDRSIZE = 14
constant OLDHDRSIZE = 12, NEWHDRSIZE = 40
constant EOF = -1

integer fn, error_code

function get_word()
-- read 2 bytes
    integer lower, upper
    
    lower = getc(fn)
    upper = getc(fn)
    if upper = EOF then
        error_code = BMP_UNEXPECTED_EOF
    end if
    return upper * 256 + lower
end function

function get_dword()
-- read 4 bytes
    integer lower, upper
    
    lower = get_word()
    upper = get_word()
    return upper * 65536 + lower
end function

function get_c_block(integer num_bytes)
-- read num_bytes bytes
    sequence s
    
    s = repeat(0, num_bytes)
    for i = 1 to num_bytes do
        s[i] = getc(fn)
    end for
    if s[length(s)] = EOF then
        error_code = BMP_UNEXPECTED_EOF
    end if
    return s
end function

function get_rgb(integer set_size)
-- get red, green, blue palette values
    integer red, green, blue
    
    blue = getc(fn)
    green = getc(fn)
    red = getc(fn)
    if set_size = 4 then
        if getc(fn) then
        end if
    end if
    return {red, green, blue}
end function

function get_rgb_block(integer num_dwords, integer set_size)
-- reads palette 
    sequence s

    s = {}
    for i = 1 to num_dwords do
        s = append(s, get_rgb(set_size))
    end for
    if s[length(s)][3] = EOF then
        error_code = BMP_UNEXPECTED_EOF
    end if
    return s
end function

function row_bytes(atom BitCount, atom Width)
-- number of bytes per row of pixel data
    return floor(((BitCount * Width) + 31) / 32) * 4
end function

function unpack(sequence image, integer BitCount, integer Width, integer Height)
-- unpack the 1-d byte sequence into a 2-d sequence of pixels
    sequence pic_2d, row, bits
    integer bytes, next_byte, byte
    
    pic_2d = {}
    bytes = row_bytes(BitCount, Width)
    next_byte = 1
    for i = 1 to Height do
        row = {}
        if BitCount = 1 then
            for j = 1 to bytes do
                byte = image[next_byte]
                next_byte += 1
                bits = repeat(0, 8)
                for k = 8 to 1 by -1 do
                    bits[k] = and_bits(byte, 1)
                    byte = floor(byte/2)
                end for
                row &= bits
            end for
        elsif BitCount = 2 then
            for j = 1 to bytes do
                byte = image[next_byte]
                next_byte += 1
                bits = repeat(0, 4)
                for k = 4 to 1 by -1 do
                    bits[k] = and_bits(byte, 3)
                    byte = floor(byte/4)
                end for
                row &= bits
            end for
        elsif BitCount = 4 then
            for j = 1 to bytes do
                byte = image[next_byte]
                row = append(row, floor(byte/16))
                row = append(row, and_bits(byte, 15))
                next_byte += 1
            end for
        elsif BitCount = 8 then
            row = image[next_byte..next_byte+bytes-1]
            next_byte += bytes
        else
            error_code = BMP_UNSUPPORTED_FORMAT
            exit
        end if
        pic_2d = prepend(pic_2d, row[1..Width])
    end for
    return pic_2d
end function

global function rReadBmp(sequence file_name)
-- read a bitmap (.BMP) file into a 2-d sequence of sequences (image)
-- return {palette,image}   
    atom Size 
    integer Type, Xhot, Yhot, Planes, BitCount
    atom Width, Height, Compression, OffBits, SizeHeader, 
         SizeImage, XPelsPerMeter, YPelsPerMeter, ClrUsed,
         ClrImportant, NumColors
    sequence Palette, Bits, two_d_bits

    error_code = 0
    fn = rOpen(file_name, "rb")
    if fn = -1 then
        return BMP_OPEN_FAILED
    end if
    Type = get_word()
    Size = get_dword()
    Xhot = get_word()
    Yhot = get_word()
    OffBits = get_dword()
    SizeHeader = get_dword()

    if SizeHeader = NEWHDRSIZE then
        Width = get_dword()
        Height = get_dword()
        Planes = get_word()
        BitCount = get_word()
        Compression = get_dword()
        if Compression != 0 then
            close(fn)
            return BMP_UNSUPPORTED_FORMAT
        end if
        SizeImage = get_dword()
        XPelsPerMeter = get_dword()
        YPelsPerMeter = get_dword()
        ClrUsed = get_dword()
        ClrImportant = get_dword()
        NumColors = (OffBits - SizeHeader - BMPFILEHDRSIZE) / 4
        if NumColors < 2 or NumColors > 256 then
            close(fn)
            return BMP_UNSUPPORTED_FORMAT
        end if
        Palette = get_rgb_block(NumColors, 4) 
    
    elsif SizeHeader = OLDHDRSIZE then 
        Width = get_word()
        Height = get_word()
        Planes = get_word()
        BitCount = get_word()
        NumColors = (OffBits - SizeHeader - BMPFILEHDRSIZE) / 3
        SizeImage = row_bytes(BitCount, Width) * Height
        Palette = get_rgb_block(NumColors, 3) 
    else
        close(fn)
        return BMP_UNSUPPORTED_FORMAT
    end if
    if Planes != 1 or Height <= 0 or Width <= 0 then
        close(fn)
        return BMP_UNSUPPORTED_FORMAT
    end if
    Bits = get_c_block(row_bytes(BitCount, Width) * Height)
    close(fn)
    two_d_bits = unpack(Bits, BitCount, Width, Height)
    if error_code then
        return error_code 
    end if
    return {Palette, two_d_bits}
end function


