/*
 * Copyright 2000-2013 JetBrains s.r.o.
 *
 * 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.intellij.vcs.log.impl;

import com.intellij.util.io.DataInputOutputUtil;
import com.intellij.vcs.log.Hash;
import org.jetbrains.annotations.NotNull;

import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
import java.util.Arrays;

/**
 * <p>The {@link Hash} implementation which stores a hash in a byte array thus saving some memory.</p>
 */
public class HashImpl implements Hash {

  private static final int SHORT_HASH_LENGTH = 7;

  @NotNull
  private final byte[] myData;
  private final int myHashCode;

  @NotNull
  public static Hash build(@NotNull String inputStr) {
    byte[] data = buildData(inputStr);
    return new HashImpl(data);
  }

  @NotNull
  public static Hash read(@NotNull DataInput in) throws IOException {
    int length = DataInputOutputUtil.readINT(in);
    byte[] buf = new byte[length];
    in.readFully(buf);
    return new HashImpl(buf);
  }

  public void write(@NotNull DataOutput out) throws IOException {
    DataInputOutputUtil.writeINT(out, myData.length);
    out.write(myData);
  }

  @NotNull
  private static byte[] buildData(@NotNull String inputStr) {
    // if length == 5, need 3 byte + 1 signal byte
    int length = inputStr.length();
    byte even = (byte)(length % 2);
    byte[] data = new byte[length / 2 + 1 + even];
    final int base = 16;
    data[0] = even;
    try {
      for (int i = 0; i < length / 2; i++) {
        int k = Character.digit(inputStr.charAt(2 * i), base) * base + Character.digit(inputStr.charAt(2 * i + 1), base);
        data[i + 1] = (byte)(k - 128);
      }
      if (even == 1) {
        int k = Character.digit(inputStr.charAt(length - 1), base);
        data[length / 2 + 1] = (byte)(k - 128);
      }
    }
    catch (NumberFormatException e) {
      throw new IllegalArgumentException("bad hash string: " + inputStr);
    }
    return data;
  }

  private HashImpl(@NotNull byte[] hash) {
    myData = hash;
    myHashCode = Arrays.hashCode(hash);
  }

  @NotNull
  @Override
  public String asString() {
    assert myData.length > 0 : "bad length Hash.data";
    byte even = myData[0];
    StringBuilder sb = new StringBuilder();
    for (int i = 1; i < myData.length; i++) {
      int k1 = (myData[i] + 128) / 16;
      int k2 = (myData[i] + 128) % 16;
      char c1 = Character.forDigit(k1, 16);
      char c2 = Character.forDigit(k2, 16);
      if (i == myData.length - 1 && even == 1) {
        sb.append(c2);
      }
      else {
        sb.append(c1).append(c2);
      }
    }
    return sb.toString();
  }

  @Override
  public boolean equals(Object o) {
    if (this == o) return true;
    if (o == null || getClass() != o.getClass()) return false;
    HashImpl that = (HashImpl)o;
    return myHashCode == that.myHashCode && Arrays.equals(myData, that.myData);
  }

  @Override
  public int hashCode() {
    return myHashCode;
  }

  @Override
  public String toString() {
    return asString();
  }

  @NotNull
  @Override
  public String toShortString() {
    String s = asString();
    return s.substring(0, Math.min(s.length(), SHORT_HASH_LENGTH));
  }
}

@@ -1015,16 +1015,54 @@
 Hash %7B%0A%0A
+  private static final int BASE = 16;%0A
   privat
@@ -1943,33 +1943,8 @@
 n%5D;%0A
-    final int base = 16;%0A
     
@@ -1963,20 +1963,8 @@
 en;%0A
-    try %7B%0A  
     
@@ -2004,34 +2004,32 @@
 ++) %7B%0A      
-  
 int k = 
 Character.di
@@ -2020,78 +2020,53 @@
 k = 
-Character.digit(inputStr.charAt(2 * i), base) * base + Character.digit
+parseChar(inputStr, 2 * i) * BASE + parseChar
 (inp
@@ -2070,24 +2070,18 @@
 inputStr
-.charAt(
+, 
 2 * i + 
@@ -2082,27 +2082,18 @@
 * i + 1)
-, base);%0A  
+;%0A
       da
@@ -2121,30 +2121,26 @@
 - 128);%0A    
-  %7D%0A  
+%7D%0A
     if (even
@@ -2144,26 +2144,24 @@
 ven == 1) %7B%0A
-  
       int k 
@@ -2166,23 +2166,17 @@
 k = 
-Character.digit
+parseChar
 (inp
@@ -2184,16 +2184,10 @@
 tStr
-.charAt(
+, 
 leng
@@ -2197,19 +2197,10 @@
 - 1)
-, base);%0A  
+;%0A
     
@@ -2249,52 +2249,180 @@
     
-  
 %7D%0A    
-%7D%0A    catch (NumberFormatException e
+return data;%0A  %7D%0A%0A  private static int parseChar(@NotNull String inputString, int index) %7B%0A    int k = Character.digit(inputString.charAt(index), BASE);%0A    if (k %3C 0
 ) %7B%0A
@@ -2488,24 +2488,27 @@
 %22 + inputStr
+ing
 );%0A    %7D%0A   
@@ -2507,36 +2507,33 @@
    %7D%0A    return 
-data
+k
 ;%0A  %7D%0A%0A  private

Throw IAE if not a hash is passed to HashImpl constructor