/*
 * Copyright 2008 ZXing authors
 *
 * 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.
 */

package com.google.zxing.qrcode.encoder;

import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel;
import com.google.zxing.qrcode.decoder.Mode;

/**
 * @author satorux@google.com (Satoru Takabayashi) - creator
 * @author dswitkin@google.com (Daniel Switkin) - ported from C++
 */
public final class QRCode {

  public static final int NUM_MASK_PATTERNS = 8;

  private Mode mode;
  private ErrorCorrectionLevel ecLevel;
  private int version;
  private int matrixWidth;
  private int maskPattern;
  private int numTotalBytes;
  private int numDataBytes;
  private int numECBytes;
  private int numRSBlocks;
  private ByteMatrix matrix;

  public QRCode() {
    mode = null;
    ecLevel = null;
    version = -1;
    matrixWidth = -1;
    maskPattern = -1;
    numTotalBytes = -1;
    numDataBytes = -1;
    numECBytes = -1;
    numRSBlocks = -1;
    matrix = null;
  }

  // Mode of the QR Code.
  public Mode getMode() {
    return mode;
  }

  // Error correction level of the QR Code.
  public ErrorCorrectionLevel getECLevel() {
    return ecLevel;
  }

  // Version of the QR Code.  The bigger size, the bigger version.
  public int getVersion() {
    return version;
  }

  // ByteMatrix width of the QR Code.
  public int getMatrixWidth() {
    return matrixWidth;
  }

  // Mask pattern of the QR Code.
  public int getMaskPattern() {
    return maskPattern;
  }

  // Number of total bytes in the QR Code.
  public int getNumTotalBytes() {
    return numTotalBytes;
  }

  // Number of data bytes in the QR Code.
  public int getNumDataBytes() {
    return numDataBytes;
  }

  // Number of error correction bytes in the QR Code.
  public int getNumECBytes() {
    return numECBytes;
  }

  // Number of Reedsolomon blocks in the QR Code.
  public int getNumRSBlocks() {
    return numRSBlocks;
  }

  // ByteMatrix data of the QR Code.
  public ByteMatrix getMatrix() {
    return matrix;
  }
  

  // Return the value of the module (cell) pointed by "x" and "y" in the matrix of the QR Code. They
  // call cells in the matrix "modules". 1 represents a black cell, and 0 represents a white cell.
  public int at(int x, int y) {
    // The value must be zero or one.
    int value = matrix.get(x, y);
    if (!(value == 0 || value == 1)) {
      // this is really like an assert... not sure what better exception to use?
      throw new RuntimeException("Bad value");
    }
    return value;
  }

  // Checks all the member variables are set properly. Returns true on success. Otherwise, returns
  // false.
  public boolean isValid() {
    return
        // First check if all version are not uninitialized.
        mode != null &&
        ecLevel != null &&
        version != -1 &&
        matrixWidth != -1 &&
        maskPattern != -1 &&
        numTotalBytes != -1 &&
        numDataBytes != -1 &&
        numECBytes != -1 &&
        numRSBlocks != -1 &&
        // Then check them in other ways..
        isValidMaskPattern(maskPattern) &&
        numTotalBytes == numDataBytes + numECBytes &&
        // ByteMatrix stuff.
        matrix != null &&
        matrixWidth == matrix.getWidth() &&
        // See 7.3.1 of JISX0510:2004 (p.5).
        matrix.getWidth() == matrix.getHeight(); // Must be square.
  }

  // Return debug String.
  public String toString() {
    StringBuffer result = new StringBuffer(200);
    result.append("<<\n");
    result.append(" mode: ");
    result.append(mode);
    result.append("\n ecLevel: ");
    result.append(ecLevel);
    result.append("\n version: ");
    result.append(version);
    result.append("\n matrixWidth: ");
    result.append(matrixWidth);
    result.append("\n maskPattern: ");
    result.append(maskPattern);
    result.append("\n numTotalBytes: ");
    result.append(numTotalBytes);
    result.append("\n numDataBytes: ");
    result.append(numDataBytes);
    result.append("\n numECBytes: ");
    result.append(numECBytes);
    result.append("\n numRSBlocks: ");
    result.append(numRSBlocks);
    if (matrix == null) {
      result.append("\n matrix: null\n");
    } else {
      result.append("\n matrix:\n");
      result.append(matrix.toString());
    }
    result.append(">>\n");
    return result.toString();
  }

  public void setMode(Mode value) {
    mode = value;
  }

  public void setECLevel(ErrorCorrectionLevel value) {
    ecLevel = value;
  }

  public void setVersion(int value) {
    version = value;
  }

  public void setMatrixWidth(int value) {
    matrixWidth = value;
  }

  public void setMaskPattern(int value) {
    maskPattern = value;
  }

  public void setNumTotalBytes(int value) {
    numTotalBytes = value;
  }

  public void setNumDataBytes(int value) {
    numDataBytes = value;
  }

  public void setNumECBytes(int value) {
    numECBytes = value;
  }

  public void setNumRSBlocks(int value) {
    numRSBlocks = value;
  }

  // This takes ownership of the 2D array.
  public void setMatrix(ByteMatrix value) {
    matrix = value;
  }

  // Check if "mask_pattern" is valid.
  public static boolean isValidMaskPattern(int maskPattern) {
    return maskPattern >= 0 && maskPattern < NUM_MASK_PATTERNS;
  }

  // Return true if the all values in the matrix are binary numbers.
  //
  // JAVAPORT: This is going to be super expensive and unnecessary, we should not call this in
  // production. I'm leaving it because it may be useful for testing. It should be removed entirely
  // if ByteMatrix is changed never to contain a -1.
  /*
  private static boolean EverythingIsBinary(final ByteMatrix matrix) {
    for (int y = 0; y < matrix.height(); ++y) {
      for (int x = 0; x < matrix.width(); ++x) {
        int value = matrix.get(y, x);
        if (!(value == 0 || value == 1)) {
          // Found non zero/one value.
          return false;
        }
      }
    }
    return true;
  }
   */

}
