// bmpParser.java
// (C) 2007 by Michael Peter Christen; mc@yacy.net, Frankfurt a. M., Germany
// first published 15.07.2007 on http://yacy.net
//
// $LastChangedDate$
// $LastChangedRevision$
// $LastChangedBy$
//
// LICENSE
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

package net.yacy.document.parser.images;

import java.awt.image.BufferedImage;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;

import javax.imageio.ImageIO;

import com.twelvemonkeys.imageio.plugins.bmp.BMPImageReader;

import net.yacy.cora.util.ConcurrentLog;

/**
 *
 * @deprecated use ImageIO {@link BMPImageReader} from github.com/haraldk/TwelveMonkeys
 *             library (imageio-bmp-3.2.jar), which as better BMP format
 *             variants support
 */
@Deprecated
public class bmpParser {

    // this is a implementation of http://de.wikipedia.org/wiki/Windows_Bitmap

    // file offsets
    private static int FILEHEADER_offset = 0;
    private static int INFOHEADER_offset = 14;
    public  static int INFOHEADER_size   = 40;

    // compression tags
    static int BI_RGB = 0;
    //private static int BI_RLE8 = 1;
    //private static int BI_RLE4 = 2;
    //private static int BI_BITFIELDS = 3;


    public static final boolean isBMP(final byte[] source) {
        // check the file magic
        return (source != null) && (source.length >= 2) && (source[0] == 'B') && (source[1] == 'M');
    }

    public static IMAGEMAP parse(final byte[] source) {
        // read info-header
        final int bfOffBits  = DWORD(source, FILEHEADER_offset + 10);

        final INFOHEADER infoheader = new INFOHEADER(source, INFOHEADER_offset);
        final COLORTABLE colortable = new COLORTABLE(source, INFOHEADER_offset + INFOHEADER_size, infoheader);

        // check consistency with bfOffBits
        assert bfOffBits == INFOHEADER_offset + 40 + colortable.colorbytes : "bfOffBits = " + bfOffBits + ", colorbytes = " + colortable.colorbytes;
        assert infoheader.biSizeImage <= source.length - bfOffBits : "bfOffBits = " + bfOffBits + ", biSizeImage = " + infoheader.biSizeImage + ", source.length = " + source.length;

        return new IMAGEMAP(source, bfOffBits, infoheader.biWidth, infoheader.biHeight, infoheader.biCompression, infoheader.biBitCount, colortable);
    }

    public static final int DWORD(final byte[] b, final int offset) {
        if (offset + 3 >= b.length) return 0;
        int ret = (b[offset + 3] & 0xff);
        ret = (ret << 8) | (b[offset + 2] & 0xff);
        ret = (ret << 8) | (b[offset + 1] & 0xff);
        ret = (ret << 8) | (b[offset] & 0xff);
        return ret;
    }

    public static final int WORD(final byte[] b, final int offset) {
        final int ret = ((b[offset + 1] & 0xff) << 8) | (b[offset] & 0xff);
        return ret;
    }

    public static final int BYTE(final byte[] b, final int offset) {
        final int ret = (b[offset] & 0xff);
        return ret;
    }


    public static class INFOHEADER {

        public int biWidth, biHeight, biBitCount, biCompression, biSizeImage, biClrUsed;

        public INFOHEADER(final byte[] s, final int offset) {
            // read info-header
            this.biWidth       = DWORD(s, offset + 4);
            this.biHeight      = DWORD(s, offset + 8);
            this.biBitCount    =  WORD(s, offset + 14);
            this.biCompression =  WORD(s, offset + 16);
            this.biSizeImage   = DWORD(s, offset + 20);
            this.biClrUsed     = DWORD(s, offset + 32);
        }
    }

    public static class COLORTABLE {

        public int colorbytes;
        public int[] colorindex;

        public COLORTABLE(final byte[] s, final int offset, final INFOHEADER infoheader) {
            // read colortable
            this.colorbytes = 0; // for consistency check
            if (infoheader.biClrUsed == 0 || infoheader.biClrUsed > 1024) {
                if ((infoheader.biBitCount == 1) || (infoheader.biBitCount == 4) || (infoheader.biBitCount == 8)) {
                    this.colorindex = new int[1 << infoheader.biBitCount];
                    this.colorbytes = 4 * this.colorindex.length;
                    int color;
                    for (int i = 0; i < this.colorindex.length; i++) {
                        // translate BGR into RGB color Scheme
                        color = 0xffffff & DWORD(s, offset + 4 * i);
                        this.colorindex[i] = color;
                    }
                } else {
                    this.colorindex = null;
                }
            } else {
                this.colorindex = new int[infoheader.biClrUsed]; // OOM DANGER!
                this.colorbytes = 4 * this.colorindex.length;
                int color;
                for (int i = 0; i < this.colorindex.length; i++) {
                    // translate BGR into RGB color Scheme
                    color = 0xffffff & DWORD(s, offset + 4 * i);
                    this.colorindex[i] = color;
                    //if (debugmode) System.out.println("Color " + i + " = " + Integer.toHexString(colorindex[i]));
                }
            }
        }
    }

    public static class IMAGEMAP {

        private BufferedImage image;

        public IMAGEMAP(final byte[] s, final int offset, final int width, final int height, final int compression, final int bitcount, final COLORTABLE colortable) {
            // parse picture content
            if ((width != 0) && (height != 0)) {
                this.image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);

                if (compression == BI_RGB) {
                    if (bitcount == 1) parseBMP1(s, offset, width, height, colortable);
                    else if (bitcount == 4) parseBMP4(s, offset, width, height, colortable);
                    else if (bitcount == 8) parseBMP8(s, offset, width, height, colortable);
                    else if (bitcount == 24) parseBMP24(s, offset, width, height);
                    else if (bitcount == 32) parseBMP32(s, offset, width, height);
                    else ConcurrentLog.fine("IMAGEPARSER", "unsupported BMP format: biCompression = " + compression + ", biBitCount = " + bitcount);
                } else {
                    ConcurrentLog.fine("IMAGEPARSER", "unsupported BMP format: biCompression = " + compression + ", biBitCount = " + bitcount);
                }
            }
        }

        private void parseBMP1(final byte[] s, final int offset, final int width, final int height, final COLORTABLE colortable) {
            int n = 0;
            int b;
            for (int rows = 0; rows < height; rows++) {
                for (int columns = 0; columns < width; columns = columns + 8) {
                    if (offset + n >= s.length) return; // emergency break
                    b = (s[offset + n] & 0xff);
                    n++;
                    this.image.setRGB(columns,     (height - rows - 1), colortable.colorindex[(b & 0x80) >> 7]);
                    this.image.setRGB(columns + 1, (height - rows - 1), colortable.colorindex[(b & 0x40) >> 6]);
                    this.image.setRGB(columns + 2, (height - rows - 1), colortable.colorindex[(b & 0x20) >> 5]);
                    this.image.setRGB(columns + 3, (height - rows - 1), colortable.colorindex[(b & 0x10) >> 4]);
                    this.image.setRGB(columns + 4, (height - rows - 1), colortable.colorindex[(b & 0x08) >> 3]);
                    this.image.setRGB(columns + 5, (height - rows - 1), colortable.colorindex[(b & 0x04) >> 2]);
                    this.image.setRGB(columns + 6, (height - rows - 1), colortable.colorindex[(b & 0x02) >> 1]);
                    this.image.setRGB(columns + 7, (height - rows - 1), colortable.colorindex[ b & 0x01]);
                }
                n += fill4(n);
            }
        }

        private void parseBMP4(final byte[] s, final int offset, final int width, final int height, final COLORTABLE colortable) {
            int n = 0;
            int b;
            for (int rows = 0; rows < height; rows++) {
                for (int columns = 0; columns < width - 1; columns = columns + 2) {
                    if (offset + n >= s.length) return; // emergency break
                    b = (s[offset + n] & 0xff);
                    n++;
                    this.image.setRGB(columns,     (height - rows - 1), colortable.colorindex[(b & 0xf0) >> 4]);
                    this.image.setRGB(columns + 1, (height - rows - 1), colortable.colorindex[b & 0xf]);
                }
                n += fill4(n);
            }
        }

        private void parseBMP8(final byte[] s, final int offset, final int width, final int height, final COLORTABLE colortable) {
            int n = 0;
            for (int rows = 0; rows < height; rows++) {
                for (int columns = 0; columns < width; columns++) {
                    if (offset + n >= s.length) return; // emergency break
                    this.image.setRGB(columns, (height - rows - 1), colortable.colorindex[(s[offset + n] & 0xff)]);
                    n++;
                }
                n += fill4(n);
            }
        }

        private void parseBMP24(final byte[] s, final int offset, final int width, final int height) {
            int n = 0;
            for (int rows = 0; rows < height; rows++) {
                for (int columns = 0; columns < width; columns++) {
                    if (offset + n + 3 >= s.length) return; // emergency break
                    this.image.setRGB(columns, (height - rows - 1), 0xffffff & DWORD(s, offset + n));
                    n += 3;
                }
                n += fill4(n);
            }
        }

        private void parseBMP32(final byte[] s, final int offset, final int width, final int height) {
            int n = 0;
            for (int rows = 0; rows < height; rows++) {
                for (int columns = 0; columns < width; columns++) {
                    if (offset + n + 3 >= s.length) return; // emergency break
                    this.image.setRGB(columns, (height - rows - 1), 0xffffff & DWORD(s, offset + n));
                    n += 4;
                }
            }
        }

        private final static int fill4(final int x) {
            final int r = x % 4;
            if (r == 0) return 0;
            return 4 - r;
        }

        public BufferedImage getImage() {
            return this.image;
        }

    }

    public static void main(final String[] args) {
        // read a bmp and write it as png
        System.setProperty("java.awt.headless", "true");
        final File in = new File(args[0]);
        final File out = new File(args[1]);

        final byte[] file = new byte[(int) in.length()];
        FileInputStream fis = null;
        try {
            fis = new FileInputStream(in);
            fis.read(file);
        } catch (final FileNotFoundException e) {
            ConcurrentLog.logException(e);
        } catch (final IOException e) {
            ConcurrentLog.logException(e);
        } finally {
        	if(fis != null) {
        		try {
        			fis.close();
        		} catch(IOException ioe) {
        			ConcurrentLog.logException(ioe);		
        		}
        	}
        }

        try {
            ImageIO.write(parse(file).getImage(), "PNG", out);
        } catch (final IOException e) {
            ConcurrentLog.logException(e);
        }
    }
}