package com.rnlibrary.barcode.decoder;

import android.graphics.Bitmap;

import com.facebook.react.bridge.Arguments;
import com.facebook.react.bridge.ReadableArray;
import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.bridge.WritableMap;

import net.sourceforge.zbar.Config;
import net.sourceforge.zbar.Image;
import net.sourceforge.zbar.ImageScanner;
import net.sourceforge.zbar.Symbol;
import net.sourceforge.zbar.SymbolSet;

import java.nio.charset.Charset;

public class ZBarDecoder implements Decoder {
    private final ImageScanner mScanner;

    public ZBarDecoder() {
        mScanner = new ImageScanner();
        mScanner.setConfig(0, Config.X_DENSITY, 2);
        mScanner.setConfig(0, Config.Y_DENSITY, 2);
    }

    @Override
    public void release() {
        mScanner.destroy();
    }

    @Override
    public void setFormats(ReadableArray formats) {
        if (formats.size() > 0) {
            // clear all formats
            mScanner.setConfig(Symbol.NONE, Config.ENABLE, 0);
            for (int i = 0; i < formats.size(); i++) {
                int format = formats.getInt(i);
                if (format != -1) {
                    mScanner.setConfig(formatToSymbol(format), Config.ENABLE, 1);
                }
            }
        } else {
            // set all formats
            mScanner.setConfig(Symbol.NONE, Config.ENABLE, 1);
        }
    }

    @Override
    public ReadableMap decodeRGBBitmap(Bitmap bitmap) {
        int width = bitmap.getWidth();
        int height = bitmap.getHeight();
        int[] pixels = new int[width * height];
        bitmap.getPixels(pixels, 0, width, 0, 0, width, height);
        bitmap.recycle();

        Image image = new Image(width, height, ImageFormat.RGB4.toString());
        image.setData(pixels);
        image = image.convert(ImageFormat.Y800.toString());

        WritableMap result = null;
        if (mScanner.scanImage(image) != 0) {
            SymbolSet symbols = mScanner.getResults();
            Symbol symbol = symbols.iterator().next();
            result = Arguments.createMap();
            result.putInt("format", symbolToFormat(symbol.getType()));
            result.putString("content", fixEncoding(symbol.getData()));
        }
        return result;
    }

    @Override
    public WritableMap decodeNV21Data(byte[] data, int width, int height, int rotation) {
        Image image = new Image(width, height, ImageFormat.Y800.toString());
        image.setData(data);

        WritableMap result = null;
        if (mScanner.scanImage(image) != 0) {
            SymbolSet symbols = mScanner.getResults();
            Symbol symbol = symbols.iterator().next();
            result = Arguments.createMap();
            result.putInt("format", symbolToFormat(symbol.getType()));
            result.putString("content", fixEncoding(symbol.getData()));
        }
        image.destroy();
        return result;
    }

    private static Charset ShiftJIS = Charset.forName("Shift_JIS");
    private static Charset UTF8 = Charset.forName("UTF-8");

    private static String fixEncoding(String content) {
        byte[] bytes = content.getBytes(ShiftJIS);
        if (content.equals(new String(bytes, ShiftJIS))) {
            return new String(bytes, UTF8);
        }
        return content;
    }

    private enum ImageFormat {
        RGB4("RGB4"),
        Y800("Y800");

        String f;

        ImageFormat(String f) {
            this.f = f;
        }

        @Override
        public String toString() {
            return f;
        }
    }

    private static int symbolToFormat(int symbol) {
        if (2 == symbol) { // Symbol.EAN2
            return 2;
        } else if (5 == symbol) { // Symbol.EAN5
            return 5;
        } else if (Symbol.EAN8 == symbol) {
            return 8;
        } else if (Symbol.UPCE == symbol) {
            return 9;
        } else if (Symbol.ISBN10 == symbol) {
            return 10;
        } else if (Symbol.UPCA == symbol) {
            return 12;
        } else if (Symbol.EAN13 == symbol) {
            return 13;
        } else if (Symbol.ISBN13 == symbol) {
            return 14;
        } else if (15 == symbol) { // Symbol.COMPOSITE
            return 15;
        } else if (Symbol.I25 == symbol) {
            return 25;
        } else if (Symbol.DATABAR == symbol) {
            return 34;
        } else if (Symbol.DATABAR_EXP == symbol) {
            return 35;
        } else if (Symbol.CODABAR == symbol) {
            return 38;
        } else if (Symbol.CODE39 == symbol) {
            return 39;
        } else if (Symbol.PDF417 == symbol) {
            return 57;
        } else if (Symbol.QRCODE == symbol) {
            return 64;
        } else if (Symbol.CODE93 == symbol) {
            return 93;
        } else if (Symbol.CODE128 == symbol) {
            return 128;
        }
        return -1;
    }

    private static int formatToSymbol(int format) {
        if (2 == format) {
            return 2; // Symbol.EAN2
        } else if (5 == format) {
            return 5; // Symbol.EAN5
        } else if (8 == format) {
            return Symbol.EAN8;
        } else if (9 == format) {
            return Symbol.UPCE;
        } else if (10 == format) {
            return Symbol.ISBN10;
        } else if (12 == format) {
            return Symbol.UPCA;
        } else if (13 == format) {
            return Symbol.EAN13;
        } else if (14 == format) {
            return Symbol.ISBN13;
        } else if (15 == format) {
            return 15; // Symbol.COMPOSITE
        } else if (25 == format) {
            return Symbol.I25;
        } else if (34 == format) {
            return Symbol.DATABAR;
        } else if (35 == format) {
            return Symbol.DATABAR_EXP;
        } else if (38 == format) {
            return Symbol.CODABAR;
        } else if (39 == format) {
            return Symbol.CODE39;
        } else if (57 == format) {
            return Symbol.PDF417;
        } else if (64 == format) {
            return Symbol.QRCODE;
        } else if (93 == format) {
            return Symbol.CODE93;
        } else if (128 == format) {
            return Symbol.CODE128;
        }
        return -1;
    }
}