/*
 * 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 "ZXBitSource.h"
#import "ZXByteArray.h"
#import "ZXCharacterSetECI.h"
#import "ZXDecoderResult.h"
#import "ZXErrors.h"
#import "ZXQRCodeDecodedBitStreamParser.h"
#import "ZXQRCodeErrorCorrectionLevel.h"
#import "ZXQRCodeMode.h"
#import "ZXQRCodeVersion.h"
#import "ZXStringUtils.h"

/**
 * See ISO 18004:2006, 6.4.4 Table 5
 */
const unichar ZX_ALPHANUMERIC_CHARS[45] = {
  '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B',
  'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N',
  'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
  ' ', '$', '%', '*', '+', '-', '.', '/', ':'
};

const int ZX_GB2312_SUBSET = 1;

@implementation ZXQRCodeDecodedBitStreamParser

+ (ZXDecoderResult *)decode:(ZXByteArray *)bytes
                    version:(ZXQRCodeVersion *)version
                    ecLevel:(ZXQRCodeErrorCorrectionLevel *)ecLevel
                      hints:(ZXDecodeHints *)hints
                      error:(NSError **)error {
  ZXBitSource *bits = [[ZXBitSource alloc] initWithBytes:bytes];
  NSMutableString *result = [NSMutableString stringWithCapacity:50];
  NSMutableArray *byteSegments = [NSMutableArray arrayWithCapacity:1];
  int symbolSequence = -1;
  int parityData = -1;

  ZXCharacterSetECI *currentCharacterSetECI = nil;
  ZXQRCodeMode *mode;
  BOOL fc1InEffect = NO;

  do {
    // While still another segment to read...
    if ([bits available] < 4) {
      // OK, assume we're done. Really, a TERMINATOR mode should have been recorded here
      mode = [ZXQRCodeMode terminatorMode];
    } else {
      mode = [ZXQRCodeMode forBits:[bits readBits:4]]; // mode is encoded by 4 bits
      if (!mode) {
        if (error) *error = ZXFormatErrorInstance();
        return nil;
      }
    }
    if (![mode isEqual:[ZXQRCodeMode terminatorMode]]) {
      if ([mode isEqual:[ZXQRCodeMode fnc1FirstPositionMode]] || [mode isEqual:[ZXQRCodeMode fnc1SecondPositionMode]]) {
        // We do little with FNC1 except alter the parsed result a bit according to the spec
        fc1InEffect = YES;
      } else if ([mode isEqual:[ZXQRCodeMode structuredAppendMode]]) {
        if (bits.available < 16) {
          if (error) *error = ZXFormatErrorInstance();
          return nil;
        }
        // sequence number and parity is added later to the result metadata
        // Read next 8 bits (symbol sequence #) and 8 bits (parity data), then continue
        symbolSequence = [bits readBits:8];
        parityData = [bits readBits:8];
      } else if ([mode isEqual:[ZXQRCodeMode eciMode]]) {
        // Count doesn't apply to ECI
        int value = [self parseECIValue:bits];
        currentCharacterSetECI = [ZXCharacterSetECI characterSetECIByValue:value];
        if (currentCharacterSetECI == nil) {
          if (error) *error = ZXFormatErrorInstance();
          return nil;
        }
      } else {
        // First handle Hanzi mode which does not start with character count
        if ([mode isEqual:[ZXQRCodeMode hanziMode]]) {
          //chinese mode contains a sub set indicator right after mode indicator
          int subset = [bits readBits:4];
          int countHanzi = [bits readBits:[mode characterCountBits:version]];
          if (subset == ZX_GB2312_SUBSET) {
            if (![self decodeHanziSegment:bits result:result count:countHanzi]) {
              if (error) *error = ZXFormatErrorInstance();
              return nil;
            }
          }
        } else {
          // "Normal" QR code modes:
          // How many characters will follow, encoded in this mode?
          int count = [bits readBits:[mode characterCountBits:version]];
          if ([mode isEqual:[ZXQRCodeMode numericMode]]) {
            if (![self decodeNumericSegment:bits result:result count:count]) {
              if (error) *error = ZXFormatErrorInstance();
              return nil;
            }
          } else if ([mode isEqual:[ZXQRCodeMode alphanumericMode]]) {
            if (![self decodeAlphanumericSegment:bits result:result count:count fc1InEffect:fc1InEffect]) {
              if (error) *error = ZXFormatErrorInstance();
              return nil;
            }
          } else if ([mode isEqual:[ZXQRCodeMode byteMode]]) {
            if (![self decodeByteSegment:bits result:result count:count currentCharacterSetECI:currentCharacterSetECI byteSegments:byteSegments hints:hints]) {
              if (error) *error = ZXFormatErrorInstance();
              return nil;
            }
          } else if ([mode isEqual:[ZXQRCodeMode kanjiMode]]) {
            if (![self decodeKanjiSegment:bits result:result count:count]) {
              if (error) *error = ZXFormatErrorInstance();
              return nil;
            }
          } else {
            if (error) *error = ZXFormatErrorInstance();
            return nil;
          }
        }
      }
    }
  } while (![mode isEqual:[ZXQRCodeMode terminatorMode]]);

  return [[ZXDecoderResult alloc] initWithRawBytes:bytes
                                              text:result.description
                                      byteSegments:byteSegments.count == 0 ? nil : byteSegments
                                           ecLevel:ecLevel == nil ? nil : ecLevel.description
                                        saSequence:symbolSequence
                                          saParity:parityData];
}


/**
 * See specification GBT 18284-2000
 */
+ (BOOL)decodeHanziSegment:(ZXBitSource *)bits result:(NSMutableString *)result count:(int)count {
  if (count * 13 > bits.available) {
    return NO;
  }

  NSMutableData *buffer = [NSMutableData dataWithCapacity:2 * count];
  while (count > 0) {
    int twoBytes = [bits readBits:13];
    int assembledTwoBytes = ((twoBytes / 0x060) << 8) | (twoBytes % 0x060);
    if (assembledTwoBytes < 0x003BF) {
      assembledTwoBytes += 0x0A1A1;
    } else {
      assembledTwoBytes += 0x0A6A1;
    }
    int8_t bytes[2];
    bytes[0] = (int8_t)((assembledTwoBytes >> 8) & 0xFF);
    bytes[1] = (int8_t)(assembledTwoBytes & 0xFF);

    [buffer appendBytes:bytes length:2];

    count--;
  }

  NSString *string = [[NSString alloc] initWithData:buffer encoding:CFStringConvertEncodingToNSStringEncoding(kCFStringEncodingGB_18030_2000)];
  if (string) {
    [result appendString:string];
  }
  return YES;
}

+ (BOOL)decodeKanjiSegment:(ZXBitSource *)bits result:(NSMutableString *)result count:(int)count {
  if (count * 13 > bits.available) {
    return NO;
  }

  NSMutableData *buffer = [NSMutableData dataWithCapacity:2 * count];
  while (count > 0) {
    int twoBytes = [bits readBits:13];
    int assembledTwoBytes = ((twoBytes / 0x0C0) << 8) | (twoBytes % 0x0C0);
    if (assembledTwoBytes < 0x01F00) {
      assembledTwoBytes += 0x08140;
    } else {
      assembledTwoBytes += 0x0C140;
    }
    int8_t bytes[2];
    bytes[0] = (int8_t)(assembledTwoBytes >> 8);
    bytes[1] = (int8_t)assembledTwoBytes;

    [buffer appendBytes:bytes length:2];

    count--;
  }

  NSString *string = [[NSString alloc] initWithData:buffer encoding:NSShiftJISStringEncoding];
  if (string) {
    [result appendString:string];
  }
  return YES;
}

+ (BOOL)decodeByteSegment:(ZXBitSource *)bits result:(NSMutableString *)result count:(int)count currentCharacterSetECI:(ZXCharacterSetECI *)currentCharacterSetECI byteSegments:(NSMutableArray *)byteSegments hints:(ZXDecodeHints *)hints {
  if (8 * count > bits.available) {
    return NO;
  }

  ZXByteArray *readBytes = [[ZXByteArray alloc] initWithLength:count];
  for (int i = 0; i < count; i++) {
    readBytes.array[i] = (int8_t)[bits readBits:8];
  }
  NSStringEncoding encoding;
  if (currentCharacterSetECI == nil) {
    encoding = [ZXStringUtils guessEncoding:readBytes hints:hints];
  } else {
    encoding = [currentCharacterSetECI encoding];
  }

  NSString *string = [[NSString alloc] initWithBytes:readBytes.array length:readBytes.length encoding:encoding];
  if (string) {
    [result appendString:string];
  }

  [byteSegments addObject:readBytes];
  return YES;
}

+ (unichar)toAlphaNumericChar:(int)value {
  if (value >= 45) {
    return -1;
  }
  return ZX_ALPHANUMERIC_CHARS[value];
}

+ (BOOL)decodeAlphanumericSegment:(ZXBitSource *)bits result:(NSMutableString *)result count:(int)count fc1InEffect:(BOOL)fc1InEffect {
  int start = (int)result.length;

  while (count > 1) {
    if ([bits available] < 11) {
      return NO;
    }
    int nextTwoCharsBits = [bits readBits:11];
    unichar next1 = [self toAlphaNumericChar:nextTwoCharsBits / 45];
    unichar next2 = [self toAlphaNumericChar:nextTwoCharsBits % 45];

    [result appendFormat:@"%C%C", next1, next2];
    count -= 2;
  }

  if (count == 1) {
    if ([bits available] < 6) {
      return NO;
    }
    unichar next1 = [self toAlphaNumericChar:[bits readBits:6]];
    [result appendFormat:@"%C", next1];
  }
  if (fc1InEffect) {
    for (int i = start; i < [result length]; i++) {
      if ([result characterAtIndex:i] == '%') {
        if (i < [result length] - 1 && [result characterAtIndex:i + 1] == '%') {
          [result deleteCharactersInRange:NSMakeRange(i + 1, 1)];
        } else {
          [result insertString:[NSString stringWithFormat:@"%C", (unichar)0x1D]
                       atIndex:i];
        }
      }
    }
  }
  return YES;
}

+ (BOOL)decodeNumericSegment:(ZXBitSource *)bits result:(NSMutableString *)result count:(int)count {
  // Read three digits at a time
  while (count >= 3) {
    // Each 10 bits encodes three digits
    if (bits.available < 10) {
      return NO;
    }
    int threeDigitsBits = [bits readBits:10];
    if (threeDigitsBits >= 1000) {
      return NO;
    }
    unichar next1 = [self toAlphaNumericChar:threeDigitsBits / 100];
    unichar next2 = [self toAlphaNumericChar:(threeDigitsBits / 10) % 10];
    unichar next3 = [self toAlphaNumericChar:threeDigitsBits % 10];

    [result appendFormat:@"%C%C%C", next1, next2, next3];
    count -= 3;
  }

  if (count == 2) {
    // Two digits left over to read, encoded in 7 bits
    if (bits.available < 7) {
      return NO;
    }
    int twoDigitsBits = [bits readBits:7];
    if (twoDigitsBits >= 100) {
      return NO;
    }
    unichar next1 = [self toAlphaNumericChar:twoDigitsBits / 10];
    unichar next2 = [self toAlphaNumericChar:twoDigitsBits % 10];
    [result appendFormat:@"%C%C", next1, next2];
  } else if (count == 1) {
    // One digit left over to read
    if (bits.available < 4) {
      return NO;
    }
    int digitBits = [bits readBits:4];
    if (digitBits >= 10) {
      return NO;
    }
    unichar next1 = [self toAlphaNumericChar:digitBits];
    [result appendFormat:@"%C", next1];
  }
  return YES;
}

+ (int)parseECIValue:(ZXBitSource *)bits {
  int firstByte = [bits readBits:8];
  if ((firstByte & 0x80) == 0) {
    return firstByte & 0x7F;
  }
  if ((firstByte & 0xC0) == 0x80) {
    int secondByte = [bits readBits:8];
    return ((firstByte & 0x3F) << 8) | secondByte;
  }
  if ((firstByte & 0xE0) == 0xC0) {
    int secondThirdBytes = [bits readBits:16];
    return ((firstByte & 0x1F) << 16) | secondThirdBytes;
  }
  return -1;
}

@end
