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

#include <png.h>

#include "wine/debug.h"
#include "wingdi.h"

static void gen_shift(DWORD mask, png_byte*shift, png_byte*bits)
{
    *shift = 0;
    *bits = 0;
    if (!mask) return;
    while (!(mask & 1)) {
        (*shift)++;
        mask >>= 1;
    }
    while (mask & 1) {
        (*bits)++;
        mask >>= 1;
    }
}

int main(int argc, char *argv[])
{
    FILE* f = fopen(argv[1], "rb");
    FILE* outf;

    if (argc != 3) {
      fprintf(stderr, "Usage: %s <bmp-in> <png-out>\n", argv[0]);
      return 1;
    }

    BITMAPFILEHEADER fh;
    union {
      DWORD size;
      BITMAPCOREINFO core;
      BITMAPINFO info;
      BITMAPV4HEADER v4;
      BITMAPV5HEADER v5;
    } bh;

    unsigned width, height, bpp, comp, size;
    int color_type;
    size_t bmp_pitch, out_pitch, i, j;
    png_byte* bmp_data;
    png_byte* out_data;
    png_byte** row_pointers;
    png_color* palette;
    png_structp png_ptr;
    png_infop info_ptr;
    png_color_8 shift, sig_bit;

    /* read bitmap file header */
    memset(&fh, 0, sizeof(fh));
    fread(&fh, sizeof(fh), 1, f);
    /* read bitmap info header */
    memset(&bh, 0, sizeof(bh));
    fread(&bh.size, sizeof(bh.size), 1, f);
    fread(&bh.size + 1, bh.size - sizeof(bh.size), 1, f);
    switch (bh.size) {
    case sizeof(BITMAPCOREHEADER):
        width = bh.core.bmciHeader.bcWidth;
        height = bh.core.bmciHeader.bcHeight;
        bpp = bh.core.bmciHeader.bcBitCount;
        comp = BI_RGB;
        size = height * ((width * bpp + 31) / 8);
        break;
    case sizeof(BITMAPINFOHEADER):
        if (bh.info.bmiHeader.biCompression == BI_BITFIELDS)
            /* this reads into the V4/V5 header's bit mask fields */
            fread(&bh.info.bmiHeader + 1, 3 * sizeof(DWORD), 1, f);
        /* fall through */
    case sizeof(BITMAPV4HEADER):
    case sizeof(BITMAPV5HEADER):
        width = bh.v5.bV5Width;
        height = bh.v5.bV5Height;
        bpp = bh.v5.bV5BitCount;
        comp = bh.v5.bV5Compression;
        size = bh.v5.bV5SizeImage;
        break;
    default:
        fprintf(stderr, "Unrecognized bitmap format\n");
        return 1;
    }

    if (bpp == 8 && comp == BI_RGB) {
        RGBQUAD entry;
        palette = malloc(sizeof(png_color) * bh.v5.bV5ClrUsed);
        for (i=0; i<bh.v5.bV5ClrUsed; i++) {
            fread(&entry, sizeof(entry), 1, f);
            palette[i].red = entry.rgbRed;
            palette[i].green = entry.rgbGreen;
            palette[i].blue = entry.rgbBlue;
        }
    }
    else palette = 0;

    fseek(f, fh.bfOffBits, SEEK_SET);
    bmp_data = malloc(size + sizeof(DWORD));
    fread(bmp_data, 1, size, f);

    fclose(f);

    if (comp != BI_BITFIELDS) {
        switch (bpp) {
        case 8:
            color_type = PNG_COLOR_TYPE_PALETTE;
            break;
        case 16:
            color_type = PNG_COLOR_TYPE_RGB;
            bh.v5.bV5RedMask   = 0x7C00;
            bh.v5.bV5GreenMask = 0x03E0;
            bh.v5.bV5BlueMask  = 0x001F;
            break;
        case 24:
        case 32:
            color_type = PNG_COLOR_TYPE_RGB;
            bh.v5.bV5RedMask   = 0xFF0000;
            bh.v5.bV5GreenMask = 0x00FF00;
            bh.v5.bV5BlueMask  = 0x0000FF;
            break;
        default:
            fprintf(stderr, "Unhandled bit depth\n");
            return 1;
        }
        if (comp != BI_RGB) {
            fprintf(stderr, "Unhandled compression\n");
            return 1;
        }
    }
    else {
        /* FIXME: check for grayscale bitmap */
        if (bh.v5.bV5AlphaMask)
            color_type = PNG_COLOR_TYPE_RGB_ALPHA;
        else
            color_type = PNG_COLOR_TYPE_RGB;
    }

    gen_shift(bh.v5.bV5RedMask,   &shift.red,   &sig_bit.red);
    gen_shift(bh.v5.bV5GreenMask, &shift.green, &sig_bit.green);
    gen_shift(bh.v5.bV5BlueMask,  &shift.blue,  &sig_bit.blue);
    gen_shift(bh.v5.bV5AlphaMask, &shift.alpha, &sig_bit.alpha);

    bmp_pitch = size / height;
    if (color_type == PNG_COLOR_TYPE_PALETTE)
        out_pitch = width;
    else
        out_pitch = width * 4;

    /* convert data */
    out_data = malloc(height * out_pitch);
    row_pointers = malloc(height * sizeof(png_byte*));
    for (i=0; i < height; i++) {
        png_byte* bmp_row = bmp_data + (height - i - 1) * bmp_pitch;
        png_byte* out_row = out_data + i * out_pitch;
        row_pointers[i] = out_row;
        if (color_type == PNG_COLOR_TYPE_PALETTE)
            memcpy(out_row, bmp_row, out_pitch);
        else for (j=0; j < width; j++) {
            DWORD data = *(DWORD*)bmp_row;
            out_row[0] = (data & bh.v5.bV5RedMask)   >> shift.red;
            out_row[1] = (data & bh.v5.bV5GreenMask) >> shift.green;
            out_row[2] = (data & bh.v5.bV5BlueMask)  >> shift.blue;
            out_row[3] = (data & bh.v5.bV5AlphaMask) >> shift.alpha;

            bmp_row += (bpp + 7) / 8;
            out_row += 4;
        }
    }

    outf = fopen(argv[2], "wb");
    png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING,
                                      NULL, NULL, NULL);
    info_ptr = png_create_info_struct(png_ptr);

    png_init_io(png_ptr, outf);

    png_set_IHDR(png_ptr, info_ptr, width, height, 8, color_type,
                 PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT,
                 PNG_FILTER_TYPE_DEFAULT);

    if (color_type == PNG_COLOR_TYPE_PALETTE)
        png_set_PLTE(png_ptr, info_ptr, palette, bh.v5.bV5ClrUsed);
    else
        png_set_sBIT(png_ptr, info_ptr, &sig_bit);

    png_write_info(png_ptr, info_ptr);

    /* png_set_invert_alpha(png_ptr); */

    if (color_type != PNG_COLOR_TYPE_PALETTE)
        png_set_shift(png_ptr, &sig_bit);

    if (color_type == PNG_COLOR_TYPE_RGB)
        png_set_filler(png_ptr, 0, PNG_FILLER_AFTER);

    png_write_image(png_ptr, row_pointers);

    png_write_end(png_ptr, info_ptr);
    png_destroy_write_struct(&png_ptr, &info_ptr);

    free(row_pointers);
    free(out_data);
    fclose(outf);

    free(bmp_data);
    if (palette) free(palette);

    return 0;
}
