/*
 * Copyright 2012 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 "ZXBitArray.h"
#import "ZXCode39Reader.h"
#import "ZXErrors.h"
#import "ZXResult.h"
#import "ZXResultPoint.h"

char CODE39_ALPHABET[] = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ-. *$/+%";
NSString *CODE39_ALPHABET_STRING = @"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ-. *$/+%";

/**
 * These represent the encodings of characters, as patterns of wide and narrow bars.
 * The 9 least-significant bits of each int correspond to the pattern of wide and narrow,
 * with 1s representing "wide" and 0s representing narrow.
 */
int CODE39_CHARACTER_ENCODINGS[44] = {
  0x034, 0x121, 0x061, 0x160, 0x031, 0x130, 0x070, 0x025, 0x124, 0x064, // 0-9
  0x109, 0x049, 0x148, 0x019, 0x118, 0x058, 0x00D, 0x10C, 0x04C, 0x01C, // A-J
  0x103, 0x043, 0x142, 0x013, 0x112, 0x052, 0x007, 0x106, 0x046, 0x016, // K-T
  0x181, 0x0C1, 0x1C0, 0x091, 0x190, 0x0D0, 0x085, 0x184, 0x0C4, 0x094, // U-*
  0x0A8, 0x0A2, 0x08A, 0x02A // $-%
};

int const CODE39_ASTERISK_ENCODING = 0x094;

@interface ZXCode39Reader ()

@property (nonatomic, assign) BOOL extendedMode;
@property (nonatomic, assign) BOOL usingCheckDigit;

- (NSString *)decodeExtended:(NSMutableString *)encoded;
- (BOOL)findAsteriskPattern:(ZXBitArray *)row a:(int *)a b:(int *)b counters:(int *)counters countersLen:(int)countersLen;
- (unichar)patternToChar:(int)pattern;
- (int)toNarrowWidePattern:(int *)counters countersLen:(unsigned int)countersLen;

@end

@implementation ZXCode39Reader

@synthesize extendedMode;
@synthesize usingCheckDigit;


/**
 * Creates a reader that assumes all encoded data is data, and does not treat the final
 * character as a check digit. It will not decoded "extended Code 39" sequences.
 */
- (id)init {
  return [self initUsingCheckDigit:NO extendedMode:NO];
}


/**
 * Creates a reader that can be configured to check the last character as a check digit.
 * It will not decoded "extended Code 39" sequences.
 */
- (id)initUsingCheckDigit:(BOOL)isUsingCheckDigit {  
  return [self initUsingCheckDigit:isUsingCheckDigit extendedMode:NO];
}


/**
 * Creates a reader that can be configured to check the last character as a check digit,
 * or optionally attempt to decode "extended Code 39" sequences that are used to encode
 * the full ASCII character set.
 */
- (id)initUsingCheckDigit:(BOOL)isUsingCheckDigit extendedMode:(BOOL)isExtendedMode {
  if (self = [super init]) {
    self.usingCheckDigit = isUsingCheckDigit;
    self.extendedMode = isExtendedMode;
  }

  return self;
}

- (ZXResult *)decodeRow:(int)rowNumber row:(ZXBitArray *)row hints:(ZXDecodeHints *)hints error:(NSError **)error {
  const int countersLen = 9;
  int counters[countersLen];
  memset(counters, 0, countersLen * sizeof(int));

  int start[2] = {0};
  if (![self findAsteriskPattern:row a:&start[0] b:&start[1] counters:counters countersLen:countersLen]) {
    if (error) *error = NotFoundErrorInstance();
    return nil;
  }
  // Read off white space
  int nextStart = [row nextSet:start[1]];
  int end = [row size];

  NSMutableString *result = [NSMutableString stringWithCapacity:20];
  unichar decodedChar;
  int lastStart;
  do {
    if (![ZXOneDReader recordPattern:row start:nextStart counters:counters countersSize:countersLen]) {
      if (error) *error = NotFoundErrorInstance();
      return nil;
    }
    int pattern = [self toNarrowWidePattern:(int *)counters countersLen:countersLen];
    if (pattern < 0) {
      if (error) *error = NotFoundErrorInstance();
      return nil;
    }
    decodedChar = [self patternToChar:pattern];
    if (decodedChar == -1) {
      if (error) *error = NotFoundErrorInstance();
      return nil;
    }
    [result appendFormat:@"%C", decodedChar];
    lastStart = nextStart;
    for (int i = 0; i < sizeof(counters) / sizeof(int); i++) {
      nextStart += counters[i];
    }
    // Read off white space
    nextStart = [row nextSet:nextStart];
  } while (decodedChar != '*');
  [result deleteCharactersInRange:NSMakeRange([result length] - 1, 1)];

  int lastPatternSize = 0;
  for (int i = 0; i < sizeof(counters) / sizeof(int); i++) {
    lastPatternSize += counters[i];
  }
  int whiteSpaceAfterEnd = nextStart - lastStart - lastPatternSize;
  if (nextStart != end && (whiteSpaceAfterEnd >> 1) < lastPatternSize) {
    if (error) *error = NotFoundErrorInstance();
    return nil;
  }

  if (usingCheckDigit) {
    int max = [result length] - 1;
    int total = 0;
    for (int i = 0; i < max; i++) {
      total += [CODE39_ALPHABET_STRING rangeOfString:[result substringWithRange:NSMakeRange(i, 1)]].location;
    }
    if ([result characterAtIndex:max] != CODE39_ALPHABET[total % 43]) {
      if (error) *error = ChecksumErrorInstance();
      return nil;
    }
    [result deleteCharactersInRange:NSMakeRange(max, 1)];
  }

  if ([result length] == 0) {
    // false positive
    if (error) *error = NotFoundErrorInstance();
    return nil;
  }

  NSString *resultString;
  if (self.extendedMode) {
    resultString = [self decodeExtended:result];
    if (!resultString) {
      if (error) *error = FormatErrorInstance();
      return nil;
    }
  } else {
    resultString = result;
  }

  float left = (float) (start[1] + start[0]) / 2.0f;
  float right = (float)(nextStart + lastStart) / 2.0f;

  return [ZXResult resultWithText:resultString
                         rawBytes:nil
                           length:0
                     resultPoints:[NSArray arrayWithObjects:[[[ZXResultPoint alloc] initWithX:left y:(float)rowNumber] autorelease],
                                   [[[ZXResultPoint alloc] initWithX:right y:(float)rowNumber] autorelease], nil]
                           format:kBarcodeFormatCode39];
}

- (BOOL)findAsteriskPattern:(ZXBitArray *)row a:(int *)a b:(int *)b counters:(int *)counters countersLen:(int)countersLen {
  int width = row.size;
  int rowOffset = [row nextSet:0];

  int counterPosition = 0;
  int patternStart = rowOffset;
  BOOL isWhite = NO;

  for (int i = rowOffset; i < width; i++) {
    if ([row get:i] ^ isWhite) {
      counters[counterPosition]++;
    } else {
      if (counterPosition == countersLen - 1) {
        if ([self toNarrowWidePattern:counters countersLen:countersLen] == CODE39_ASTERISK_ENCODING &&
            [row isRange:MAX(0, patternStart - ((i - patternStart) >> 1)) end:patternStart value:NO]) {
          if (a) *a = patternStart;
          if (b) *b = i;
          return YES;
        }
        patternStart += counters[0] + counters[1];
        for (int y = 2; y < countersLen; y++) {
          counters[y - 2] = counters[y];
        }
        counters[countersLen - 2] = 0;
        counters[countersLen - 1] = 0;
        counterPosition--;
      } else {
        counterPosition++;
      }
      counters[counterPosition] = 1;
      isWhite = !isWhite;
    }
  }

  return NO;
}

- (int)toNarrowWidePattern:(int *)counters countersLen:(unsigned int)countersLen {
  int numCounters = countersLen;
  int maxNarrowCounter = 0;
  int wideCounters;
  do {
    int minCounter = NSIntegerMax;
    for (int i = 0; i < numCounters; i++) {
      int counter = counters[i];
      if (counter < minCounter && counter > maxNarrowCounter) {
        minCounter = counter;
      }
    }
    maxNarrowCounter = minCounter;
    wideCounters = 0;
    int totalWideCountersWidth = 0;
    int pattern = 0;
    for (int i = 0; i < numCounters; i++) {
      int counter = counters[i];
      if (counters[i] > maxNarrowCounter) {
        pattern |= 1 << (numCounters - 1 - i);
        wideCounters++;
        totalWideCountersWidth += counter;
      }
    }
    if (wideCounters == 3) {
      for (int i = 0; i < numCounters && wideCounters > 0; i++) {
        int counter = counters[i];
        if (counters[i] > maxNarrowCounter) {
          wideCounters--;
          if ((counter << 1) >= totalWideCountersWidth) {
            return -1;
          }
        }
      }
      return pattern;
    }
  } while (wideCounters > 3);
  return -1;
}

- (unichar)patternToChar:(int)pattern {
  for (int i = 0; i < sizeof(CODE39_CHARACTER_ENCODINGS) / sizeof(int); i++) {
    if (CODE39_CHARACTER_ENCODINGS[i] == pattern) {
      return CODE39_ALPHABET[i];
    }
  }
  return -1;
}

- (NSString *)decodeExtended:(NSMutableString *)encoded {
  int length = [encoded length];
  NSMutableString *decoded = [NSMutableString stringWithCapacity:length];

  for (int i = 0; i < length; i++) {
    unichar c = [encoded characterAtIndex:i];
    if (c == '+' || c == '$' || c == '%' || c == '/') {
      unichar next = [encoded characterAtIndex:i + 1];
      unichar decodedChar = '\0';

      switch (c) {
      case '+':
        if (next >= 'A' && next <= 'Z') {
          decodedChar = (unichar)(next + 32);
        } else {
          return nil;
        }
        break;
      case '$':
        if (next >= 'A' && next <= 'Z') {
          decodedChar = (unichar)(next - 64);
        } else {
          return nil;
        }
        break;
      case '%':
        if (next >= 'A' && next <= 'E') {
          decodedChar = (unichar)(next - 38);
        } else if (next >= 'F' && next <= 'W') {
          decodedChar = (unichar)(next - 11);
        } else {
          return nil;
        }
        break;
      case '/':
        if (next >= 'A' && next <= 'O') {
          decodedChar = (unichar)(next - 32);
        } else if (next == 'Z') {
          decodedChar = ':';
        } else {
          return nil;
        }
        break;
      }
      [decoded appendFormat:@"%C", decodedChar];
      i++;
    } else {
      [decoded appendFormat:@"%C", c];
    }
  }

  return decoded;
}

@end
