/*
 * Copyright 2013 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.
 */

#import "ZXBitMatrix.h"
#import "ZXByteMatrix.h"
#import "ZXDataMatrixErrorCorrection.h"
#import "ZXDataMatrixWriter.h"
#import "ZXDefaultPlacement.h"
#import "ZXDimension.h"
#import "ZXEncodeHints.h"
#import "ZXHighLevelEncoder.h"
#import "ZXSymbolInfo.h"
#import "ZXSymbolShapeHint.h"

@implementation ZXDataMatrixWriter

- (ZXBitMatrix *)encode:(NSString *)contents format:(ZXBarcodeFormat)format width:(int)width height:(int)height error:(NSError **)error {
  return [self encode:contents format:format width:width height:height hints:nil error:error];
}

- (ZXBitMatrix *)encode:(NSString *)contents format:(ZXBarcodeFormat)format width:(int)width height:(int)height hints:(ZXEncodeHints *)hints error:(NSError **)error {
  if (contents.length == 0) {
    [NSException raise:NSInvalidArgumentException format:@"Found empty contents"];
  }

  if (format != kBarcodeFormatDataMatrix) {
    [NSException raise:NSInvalidArgumentException format:@"Can only encode kBarcodeFormatDataMatrix"];
  }

  if (width < 0 || height < 0) {
    [NSException raise:NSInvalidArgumentException
                format:@"Requested dimensions are too small: %dx%d", width, height];
  }

  // Try to get force shape & min / max size
  ZXSymbolShapeHint *shape = [ZXSymbolShapeHint forceNone];
  ZXDimension *minSize = nil;
  ZXDimension *maxSize = nil;
  if (hints != nil) {
    ZXSymbolShapeHint *requestedShape = hints.dataMatrixShape;
    if (requestedShape != nil) {
      shape = requestedShape;
    }
    ZXDimension *requestedMinSize = hints.minSize;
    if (requestedMinSize != nil) {
      minSize = requestedMinSize;
    }
    ZXDimension *requestedMaxSize = hints.maxSize;
    if (requestedMaxSize != nil) {
      maxSize = requestedMaxSize;
    }
  }

  //1. step: Data encodation
  NSString *encoded = [ZXHighLevelEncoder encodeHighLevel:contents shape:shape minSize:minSize maxSize:maxSize];

  ZXSymbolInfo *symbolInfo = [ZXSymbolInfo lookup:encoded.length shape:shape minSize:minSize maxSize:maxSize fail:YES];

  //2. step: ECC generation
  NSString *codewords = [ZXDataMatrixErrorCorrection encodeECC200:encoded symbolInfo:symbolInfo];

  //3. step: Module placement in Matrix
  ZXDefaultPlacement *placement = [[ZXDefaultPlacement alloc] initWithCodewords:codewords numcols:symbolInfo.symbolDataWidth numrows:symbolInfo.symbolDataHeight];
  [placement place];

  //4. step: low-level encoding
  return [self encodeLowLevel:placement symbolInfo:symbolInfo];
}

/**
 * Encode the given symbol info to a bit matrix.
 */
- (ZXBitMatrix *)encodeLowLevel:(ZXDefaultPlacement *)placement symbolInfo:(ZXSymbolInfo *)symbolInfo {
  int symbolWidth = symbolInfo.symbolDataWidth;
  int symbolHeight = symbolInfo.symbolDataHeight;

  ZXByteMatrix *matrix = [[ZXByteMatrix alloc] initWithWidth:symbolInfo.symbolWidth height:symbolInfo.symbolHeight];

  int matrixY = 0;

  for (int y = 0; y < symbolHeight; y++) {
    // Fill the top edge with alternate 0 / 1
    int matrixX;
    if ((y % symbolInfo.matrixHeight) == 0) {
      matrixX = 0;
      for (int x = 0; x < symbolInfo.symbolWidth; x++) {
        [matrix setX:matrixX y:matrixY boolValue:(x % 2) == 0];
        matrixX++;
      }
      matrixY++;
    }
    matrixX = 0;
    for (int x = 0; x < symbolWidth; x++) {
      // Fill the right edge with full 1
      if ((x % symbolInfo.matrixWidth) == 0) {
        [matrix setX:matrixX y:matrixY boolValue:YES];
        matrixX++;
      }
      [matrix setX:matrixX y:matrixY boolValue:[placement bitAtCol:x row:y]];
      matrixX++;
      // Fill the right edge with alternate 0 / 1
      if ((x % symbolInfo.matrixWidth) == symbolInfo.matrixWidth - 1) {
        [matrix setX:matrixX y:matrixY boolValue:(y % 2) == 0];
        matrixX++;
      }
    }
    matrixY++;
    // Fill the bottom edge with full 1
    if ((y % symbolInfo.matrixHeight) == symbolInfo.matrixHeight - 1) {
      matrixX = 0;
      for (int x = 0; x < symbolInfo.symbolWidth; x++) {
        [matrix setX:matrixX y:matrixY boolValue:YES];
        matrixX++;
      }
      matrixY++;
    }
  }

  return [self convertByteMatrixToBitMatrix:matrix];
}

/**
 * Convert the ByteMatrix to BitMatrix.
 */
- (ZXBitMatrix *)convertByteMatrixToBitMatrix:(ZXByteMatrix *)matrix {
  int matrixWidgth = matrix.width;
  int matrixHeight = matrix.height;

  ZXBitMatrix *output = [[ZXBitMatrix alloc] initWithWidth:matrixWidgth height:matrixHeight];
  [output clear];
  for (int i = 0; i < matrixWidgth; i++) {
    for (int j = 0; j < matrixHeight; j++) {
      // Zero is white in the bytematrix
      if ([matrix getX:i y:j] == 1) {
        [output setX:i y:j];
      }
    }
  }

  return output;
}

@end
