/*
 * Copyright 2012 Jeremias Maerki.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/* $Id: QRCodeBean.java,v 1.3 2012-02-08 12:59:41 jmaerki Exp $ */

package org.krysalis.barcode4j.impl.qr;

import java.awt.Dimension;

import org.krysalis.barcode4j.BarcodeDimension;
import org.krysalis.barcode4j.TwoDimBarcodeLogicHandler;
import org.krysalis.barcode4j.impl.AbstractBarcodeBean;
import org.krysalis.barcode4j.impl.DefaultTwoDimCanvasLogicHandler;
import org.krysalis.barcode4j.output.Canvas;
import org.krysalis.barcode4j.output.CanvasProvider;
import org.krysalis.barcode4j.tools.ECIUtil;
import org.krysalis.barcode4j.tools.UnitConv;

import com.google.zxing.WriterException;
import com.google.zxing.qrcode.encoder.ByteMatrix;
import com.google.zxing.qrcode.encoder.Encoder;
import com.google.zxing.qrcode.encoder.QRCode;

/**
 * This class is an implementation of QR Code (ISO 18004:2006(E)).
 *
 * @version $Id: QRCodeBean.java,v 1.3 2012-02-08 12:59:41 jmaerki Exp $
 */
public class QRCodeBean extends AbstractBarcodeBean {

    /** The default module width (dot size) for DataMatrix. */
    protected static final double DEFAULT_MODULE_WIDTH = UnitConv.in2mm(1.0 / 72); //1px at 72dpi

    /** Error correction level */
    private char errorCorrectionLevel = QRConstants.ERROR_CORRECTION_LEVEL_L;
    /** Message encoding */
    private String encoding = "ISO-8859-1"; //ECI 000003
    /** Optional: the minimum size of the symbol. */
    private Dimension minSize;
    /** Optional: the maximum size of the symbol. */
    private Dimension maxSize;

    /** Create a new instance. */
    public QRCodeBean() {
        this.height = 0.0; //not used by DataMatrix
        this.moduleWidth = DEFAULT_MODULE_WIDTH;
        setQuietZone(QRConstants.QUIET_ZONE_SIZE * moduleWidth);
    }

    /**
     * Sets the error correction level.
     * @param level the error correction level (one of L, M, Q and H)
     */
    public void setErrorCorrectionLevel(char level) {
        switch (level) {
        case QRConstants.ERROR_CORRECTION_LEVEL_L:
        case QRConstants.ERROR_CORRECTION_LEVEL_M:
        case QRConstants.ERROR_CORRECTION_LEVEL_Q:
        case QRConstants.ERROR_CORRECTION_LEVEL_H:
            this.errorCorrectionLevel = level;
            break;
        default:
            throw new IllegalArgumentException(
                    "Invalid error correction level. Valid levels are: L, M, Q and H");
        }
    }

    /**
     * Returns the selected error correction level.
     * @return the error correction level (one of L, M, Q and H)
     */
    public char getErrorCorrectionLevel() {
        return this.errorCorrectionLevel;
    }

    /**
     * Sets the message encoding. The value must conform to one of Java's encodings and
     * have a mapping in the ECI registry.
     * @param encoding the message encoding
     */
    public void setEncoding(String encoding) {
        if (ECIUtil.getECIForEncoding(encoding) < 0) {
            throw new IllegalArgumentException("Not a valid encoding: " + encoding);
        }
        this.encoding = encoding;
    }

    /**
     * Returns the message encoding.
     * @return the message encoding (default is "ISO-8859-1")
     */
    public String getEncoding() {
        return this.encoding;
    }

    /**
     * Sets the minimum symbol size that is to be produced.
     * @param minSize the minimum size (in pixels), or null for no constraint
     */
    public void setMinSize(Dimension minSize) {
        this.minSize = (minSize != null ? new Dimension(minSize) : null);
    }

    /**
     * Returns the minimum symbol size that is to be produced. If the method returns null,
     * there's no constraint on the symbol size.
     * @return the minimum symbol size (in pixels), or null if there's no size constraint
     */
    public Dimension getMinSize() {
        if (this.minSize != null) {
            return new Dimension(this.minSize);
        } else {
            return null;
        }
    }

    /**
     * Sets the maximum symbol size that is to be produced.
     * @param maxSize the maximum size (in pixels), or null for no constraint
     */
    public void setMaxSize(Dimension maxSize) {
        this.maxSize = (maxSize != null ? new Dimension(maxSize) : null);
    }

    /**
     * Returns the maximum symbol size that is to be produced. If the method returns null,
     * there's no constraint on the symbol size.
     * @return the maximum symbol size (in pixels), or null if there's no size constraint
     */
    public Dimension getMaxSize() {
        if (this.maxSize != null) {
            return new Dimension(this.maxSize);
        } else {
            return null;
        }
    }

    /** {@inheritDoc} */
    public void generateBarcode(CanvasProvider canvas, String msg) {
        if ((msg == null)
                || (msg.length() == 0)) {
            throw new NullPointerException("Parameter msg must not be empty");
        }

        TwoDimBarcodeLogicHandler handler =
                new DefaultTwoDimCanvasLogicHandler(this, new Canvas(canvas));

        QRLogicImpl impl = new QRLogicImpl();
        impl.generateBarcodeLogic(handler, msg, encoding, errorCorrectionLevel,
                getMinSize(), getMaxSize());
    }

    /** {@inheritDoc} */
    public BarcodeDimension calcDimensions(String msg) {
        QRCode code = new QRCode();
        try {
        	code = Encoder.encode(msg,
                    QRLogicImpl.getZXingErrorLevel(errorCorrectionLevel),
                    QRLogicImpl.createHints(encoding));
        } catch (WriterException e) {
            throw new RuntimeException(e.getMessage());
        }
        ByteMatrix matrix = code.getMatrix();
        int effWidth = matrix.getWidth();
        int effHeight = matrix.getHeight();
        checkSizeConstraints(effWidth, effHeight);

        double width = effWidth * getModuleWidth();
        double height = effHeight * getBarHeight();
        double qzh = (hasQuietZone() ? getQuietZone() : 0);
        double qzv = (hasQuietZone() ? getVerticalQuietZone() : 0);
        return new BarcodeDimension(width, height,
                width + (2 * qzh), height + (2 * qzv),
                qzh, qzv);
    }

    private void checkSizeConstraints(int width, int height) {
        //Note: we're only checking the constraints, we can't currently influence ZXing's encoder.
        if (this.minSize != null) {
            if (width < this.minSize.width || height < this.minSize.height) {
                throw new IllegalArgumentException(
                        "The given message would result in a smaller symbol than required."
                        + " Requested minimum: "
                        + this.minSize.width + " x " + this.minSize.height
                        + ", effective: "
                        + width + " x " + height);
            }
        }
        if (this.maxSize != null) {
            if (width > this.maxSize.width || height > this.maxSize.height) {
                throw new IllegalArgumentException(
                        "The given message would result in a larger symbol than required."
                        + " Requested maximum: "
                        + this.maxSize.width + " x " + this.maxSize.height
                        + ", effective: "
                        + width + " x " + height);
            }
        }
    }

    /** {@inheritDoc} */
    public double getVerticalQuietZone() {
        return getQuietZone();
    }

    /** {@inheritDoc} */
    public double getBarWidth(int width) {
        return moduleWidth;
    }

    /** {@inheritDoc} */
    public double getBarHeight() {
        return moduleWidth;
    }

}