package com.github.ricotilgner.nxtreedsolomon;

import java.math.BigInteger;
import java.util.Arrays;

import com.google.zxing.common.reedsolomon.GenericGF;
import com.google.zxing.common.reedsolomon.ReedSolomonDecoder;
import com.google.zxing.common.reedsolomon.ReedSolomonEncoder;
import com.google.zxing.common.reedsolomon.ReedSolomonException;

public class NxtReedSolomon {
  private static final String charSet = "23456789ABCDEFGHJKLMNPQRSTUVWXYZ";
  private static final BigInteger POW_2_64 = BigInteger.valueOf(2).pow(64);
  private static final int DATA_LENGTH = 13;
  private static final int RS_LENGTH = 4;
  private static final int MESSAGE_LENGTH = DATA_LENGTH + RS_LENGTH;

  public static void main(String[] args) {
    BigInteger account = new BigInteger(args[0]);
    System.out.println(account.toString());
    String encoded = encode(account);
    System.out.println("encoded: " + encoded);
    try {
      System.out.println(decode(args[1]));
    } catch (IllegalArgumentException e) {
      System.out.println(e.getMessage());
    } catch (CorrectionException e) {
      System.out.println("Did you mean: " + e.getCorrected());
    }
  }

  public static String encode(BigInteger account)
      throws IllegalArgumentException {
    if (account.compareTo(POW_2_64) >= 0) {
      throw new IllegalArgumentException("Account number is bigger than 2^64.");
    }
    if (account.compareTo(BigInteger.ZERO) < 0) {
      throw new IllegalArgumentException("Account number is negative.");
    }
    int[] accountBase32 = new int[MESSAGE_LENGTH];
    for (int i = 0; i < DATA_LENGTH; i++) {
      accountBase32[i] =
          account.shiftRight(60 - 5 * i).and(BigInteger.valueOf(31)).intValue();
    }
    new ReedSolomonEncoder(GenericGF.ITPP_32).encode(accountBase32, RS_LENGTH);
    return base32ToString(accountBase32);
  }

  private static String base32ToString(int[] base32) {
    StringBuffer output = new StringBuffer("NXT");
    for (int i = 0; i < base32.length; i++) {
      if (i % 5 == 2) {
        output.append("-");
      }
      output.append(charSet.charAt(base32[i]));
    }
    return output.toString();
  }

  public static BigInteger decode(String account)
      throws IllegalArgumentException, CorrectionException {
    account = account.trim().replace("-", "");
    if (!account.startsWith("NXT")) {
      throw new IllegalArgumentException("Account is not an NXT account.");
    }
    account = account.substring(3);
    if (account.length() != MESSAGE_LENGTH) {
      throw new IllegalArgumentException("Account name has the wrong length.");
    }
    if (!account.matches("^[2-9A-HJ-NP-Z]+$")) {
      throw new IllegalArgumentException("Account name contains illegal characters.");
    }
    int[] accountBase32 = new int[MESSAGE_LENGTH];
    for (int i = 0; i < MESSAGE_LENGTH; i++) {
      accountBase32[i] = charSet.indexOf(account.charAt(i));
    }
    int[] inputAccountBase32 = Arrays.copyOf(accountBase32, accountBase32.length);
    try {
      new ReedSolomonDecoder(GenericGF.ITPP_32).decode(accountBase32, RS_LENGTH);
    } catch (ReedSolomonException e) {
      throw new IllegalArgumentException("Account does not exist.");
    }
    if (!Arrays.equals(inputAccountBase32, accountBase32)) {
      throw new CorrectionException(base32ToString(accountBase32));
    }
    BigInteger result = BigInteger.ZERO;
    for (int i = 0; i < DATA_LENGTH; i++) {
      result = result.add(BigInteger.valueOf(accountBase32[i])
          .and(BigInteger.valueOf(31)).shiftLeft(60 - 5 * i));
    }
    // If the input contained more errors than RS could handle, we can still make sure
    // the 1 Bit we had to append while encoding are 0.
    if (result.compareTo(POW_2_64) >= 0) {
      throw new IllegalArgumentException("Account does not exist.");
    }
    return result;
  }
}

@@ -853,16 +853,47 @@
 (account
+, Boolean.parseBoolean(args%5B1%5D)
 );%0A    S
@@ -977,25 +977,25 @@
 decode(args%5B
-1
+2
 %5D));%0A    %7D c
@@ -1224,32 +1224,48 @@
 gInteger account
+, boolean prefix
 )%0A      throws I
@@ -1876,16 +1876,24 @@
 ntBase32
+, prefix
 );%0A  %7D%0A%0A
@@ -1943,16 +1943,32 @@
 %5D base32
+, boolean prefix
 ) %7B%0A    
@@ -1980,21 +1980,21 @@
 gBuffer 
-outpu
+resul
 t = new 
@@ -2010,16 +2010,57 @@
 fer(
+);%0A    if (prefix) %7B%0A      result.append(
 %22NXT%22);%0A
     
@@ -2051,24 +2051,30 @@
 end(%22NXT%22);%0A
+    %7D%0A
     for (int
@@ -2135,29 +2135,29 @@
 ) %7B%0A        
-outpu
+resul
 t.append(%22-%22
@@ -2173,21 +2173,21 @@
 %7D%0A      
-outpu
+resul
 t.append
@@ -2236,13 +2236,13 @@
 urn 
-outpu
+resul
 t.to
@@ -2400,64 +2400,191 @@
 m().
-replace(%22-%22, %22%22);%0A    if (!account.startsWith(%22NXT%22)
+toUpperCase();%0A    // old decimal format%0A    if (account.matches(%22%5E%5B1-9%5D%5B0-9%5D*$%22)) %7B%0A      BigInteger result = new BigInteger(account);%0A      if (result.compareTo(POW_2_64) %3E= 0
 ) %7B%0A
+  
     
@@ -2663,45 +2663,238 @@
     
-%7D%0A    account = account.substring(3);
+  %7D%0A      return result;%0A    %7D%0A    account = account.replace(%22-%22, %22%22);%0A    // the prefix is optional%0A    boolean prefix = false;%0A    if (account.startsWith(%22NXT%22)) %7B%0A      account = account.substring(3);%0A      prefix = true;%0A    %7D
 %0A   
@@ -3743,16 +3743,24 @@
 ntBase32
+, prefix
 ));%0A    
@@ -4114,19 +4114,18 @@
 ncoding 
-are
+is
  0.%0A    

make prefix optional, handle old account numbers, handle lower case