// A library to allocate, load and save 24 bit bitmap files.
// Written by Nils Liaaen Corneliusen 2014.
// License: CC0 1.0 Universal (CC0 1.0) Public Domain Dedication license
// Please refer to the article at https://www.ignorantus.com/pages/tilegx_bilinear/ for more information.

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

#include "bmp.h"

#define MAXW (1920*8)
#define MAXH (1080*8)

bmp_argb *bmp_alloc( unsigned int w, unsigned int h )
{
    if( w == 0 || w > MAXW || h == 0 || h > MAXH ) {
        printf( "Illegal dimensions %d*%d!\n", w, h );
        return NULL;
    }

    bmp_argb *bp = malloc( sizeof(bmp_argb) );
    if( bp == NULL ) {
        perror( "malloc" );
        return NULL;
    }

    bp->w      = w;
    bp->h      = h;

    unsigned int h8  = (h+2+7)&~7;       // +2 for pad area lower, remove if not needed
    unsigned int w64 = ((w+2+63)&~63)*4; // +2 for pad area right, remove if not needed

    bp->stride = w64;

    bp->argb = malloc( bp->stride * h8 );
    if( bp->argb == NULL ) {
        perror( "malloc" );
        free( bp );
        bp = NULL;
    }

    return bp;
}

void bmp_free( bmp_argb *bp )
{
    free( bp->argb );
    free( bp );
}

uint8_t bmpheader[] = {
    0x42, 0x4D, 0x36, 0x90,
    0x12, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x36, 0x00,
    0x00, 0x00, 0x28, 0x00,
    0x00, 0x00, 0xC0, 0x02,
    0x00, 0x00, 0x40, 0x02,
    0x00, 0x00, 0x01, 0x00,
    0x18, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x90,
    0x12, 0x00, 0x13, 0x0B,
    0x00, 0x00, 0x13, 0x0B,
    0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00,
    0x00, 0x00
};

bmp_argb *bmp_load( const char *fname, bool flip )
{
    printf( "Loading bitmap %s\n", fname );

    FILE *fp = fopen( fname, "rb" );
    if( fp == NULL  ) {
        printf( "Could not open %s!\n", fname );
        return NULL;
    }

    uint8_t hdr[sizeof(bmpheader)];

    int rc = fread( hdr, 1, sizeof(bmpheader), fp );
    if( rc != sizeof(bmpheader) ) {
        printf( "Wrong header size! %d, expected %d\n", rc, (int)sizeof(bmpheader) );
        fclose( fp );
        return false;
    }

    if( hdr[0] != 0x42 || hdr[1] != 0x4D ) {
        printf( "Wrong header!\n" );
        fclose( fp );
        return false;
    }

    unsigned int w  = hdr[18]|(hdr[19]<<8);
    unsigned int h  = hdr[22]|(hdr[23]<<8);

    if( w == 0 || w > MAXW || h == 0 || h > MAXH ) {
        printf( "Illegal dimensions %d*%d!\n", w, h );
        fclose( fp );
        return NULL;
    }


    bmp_argb *bp = bmp_alloc( w, h );
    if( bp == NULL ) {
        fclose( fp );
        return NULL;
    }

    unsigned int rowbytes = ((bp->w*3)+3)&~0x03;

    uint8_t *row = malloc( rowbytes );
    if( row == NULL ) {
        perror( "malloc" );
        bmp_free( bp );
        fclose( fp );
        return NULL;
    }

    for( unsigned int y = 0; y < bp->h; y++ ) {

        unsigned int dsty = flip ? bp->h-1-y : y;
        uint32_t *argb = (uint32_t *)(bp->argb + dsty*bp->stride);

        rc = fread( row, 1, rowbytes, fp );
        if( rc != rowbytes ) {
            printf( "Error reading from file %s, row %d! Got %d, expected %d\n", fname, y, rc, rowbytes );
            bmp_free( bp );
            fclose( fp );
            free( row );
            return NULL;
        }

        for( unsigned int x = 0; x < bp->w*3; x += 3 ) {
            *argb++ = (row[x+2]<<16)|(row[x+1]<<8)|row[x+0];
        }

    }

    fclose( fp );
    free( row );

    return bp;
}

bool bmp_save( bmp_argb *bp, const char *fname, bool flip )
{
    unsigned int rowbytes = ((bp->w*3)+3)&~0x03;

    printf( "Saving bitmap %s\n", fname );

    FILE *fp = fopen( fname, "wb" );
    if( fp == NULL ) {
		printf( "Error: Could not create ouput file %s\n", fname );
        return false;
	}

    uint8_t hdr[sizeof(bmpheader)];

    memcpy( hdr, bmpheader, sizeof(bmpheader) );

    unsigned int size = sizeof(bmpheader) + rowbytes * bp->h;

    hdr[2] = (size>> 0)&0xff;
    hdr[3] = (size>> 8)&0xff;
    hdr[4] = (size>>16)&0xff;
    hdr[5] = (size>>24)&0xff;

    hdr[18] = (bp->w)&0xff;
    hdr[19] = ((bp->w)>>8)&0xff;

    hdr[22] = (bp->h)&0xff;
    hdr[23] = ((bp->h)>>8)&0xff;

	int rc = fwrite( hdr, 1, sizeof(bmpheader), fp );
	if( rc != sizeof(bmpheader) ) {
        printf( "Error: Header truncated!\n" );
        fclose( fp );
        return false;
    }

    uint8_t *row = malloc( rowbytes );

    for( unsigned int y = 0; y < bp->h; y++ ) {

        unsigned int srcy = flip ? bp->h-1-y : y;
        uint32_t *argb = (uint32_t *)(bp->argb + srcy*bp->stride);

        for( int x = 0; x < bp->w*3; x += 3 ) {
            uint32_t pixel = *argb++;
            row[x+0] =  pixel     &0xff;
            row[x+1] = (pixel>>8) &0xff;
            row[x+2] = (pixel>>16)&0xff;
        }

        rc = fwrite( row, 1, rowbytes, fp );
        if( rc != rowbytes ) {
            printf( "Error writing to file %s, row %d! Got %d, expected %d\n", fname, y, rc, rowbytes );
            fclose( fp );
            free( row );
            return false;
        }

    }

    fclose( fp );
    free( row );

    return true;
}
