/*
 * Copyright (c) 2001-2017, Zoltan Farkas All Rights Reserved.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 *
 * Additionally licensed with:
 *
 * 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 org.spf4j.base;

import com.google.common.annotations.Beta;
import com.google.common.annotations.GwtCompatible;
import com.google.common.annotations.GwtIncompatible;
import com.google.common.collect.ObjectArrays;
import java.lang.annotation.Annotation;
import java.lang.reflect.Array;

/**
 * Array utilities.
 *
 * @author zoly
 */
@GwtCompatible
public final class Arrays {

  public static final Object[] EMPTY_OBJ_ARRAY = new Object[]{};

  public static final Annotation[] EMPTY_ANNOT_ARRAY = new Annotation[]{};

  public static final Class[] EMPTY_CLASS_ARRAY = new Class[]{};

  public static final String[] EMPTY_STRING_ARRAY = new String[]{};

  public static final byte[] EMPTY_BYTE_ARRAY = new byte[]{};

  public static final char[] EMPTY_CHAR_ARRAY = new char[]{};

  public static final boolean[] EMPTY_BOOLEAN_ARRAY = new boolean[]{};

  public static final long[] EMPTY_LONG_ARRAY = new long[]{};

  public static final int[] EMPTY_INT_ARRAY = new int[]{};

  private static final int ARR_CPY_THR = 128;

  private Arrays() {
  }

  public static double[] getColumnAsDoubles(final long[][] data, final int columnNumber) {
    double[] result = new double[data.length];
    for (int i = 0; i < result.length; i++) {
      result[i] = data[i][columnNumber];
    }
    return result;
  }

  public static double[] getColumn(final double[][] data, final int columnNumber) {
    double[] result = new double[data.length];
    for (int i = 0; i < result.length; i++) {
      result[i] = data[i][columnNumber];
    }
    return result;
  }

  public static double[] toDoubleArray(final long... larr) {
    double[] result = new double[larr.length];
    for (int i = 0; i < larr.length; i++) {
      result[i] = larr[i];
    }
    return result;
  }

  public static Object[] toObjectArray(final long... larr) {
    Object[] result = new Object[larr.length];
    for (int i = 0; i < larr.length; i++) {
      result[i] = larr[i];
    }
    return result;
  }

  public static double[] divide(final double[] arr1, final double[] arr2) {
    double[] result = new double[arr1.length];
    for (int i = 0; i < result.length; i++) {
      result[i] = arr1[i] / arr2[i];
    }
    return result;
  }

  public static boolean deepEquals(final Object[] a1, final Object[] a2, final int starting) {
    return deepEquals(a1, a2, starting, a1.length);
  }

  public static boolean deepEquals(final Object[] a1, final Object[] a2, final int starting, final int ending) {
    if (a1 == a2) {
      return true;
    }
    if (a1 == null || a2 == null) {
      return false;
    }
    for (int i = starting; i < ending; i++) {
      if (!java.util.Objects.deepEquals(a1[i], a2[i])) {
        return false;
      }
    }
    return true;
  }

  public static boolean equals(final byte[] a, final byte[] b, final int a1,  final int b1, final int length) {
    for (int i = a1, j = b1, k = 0; k < length; i++, j++, k++) {
      if (a[i] != b[j]) {
        return false;
      }
    }
    return true;
  }


  public static int search(final char[] array, final char c) {
    for (int i = 0; i < array.length; i++) {
      if (array[i] == c) {
        return i;
      }
    }
    return -1;
  }

  public static <T> T[] moveOfRange(final T[] original, final int from, final int to) {
    int newLength = to - from;
    if (newLength < 0) {
      throw new IllegalArgumentException(from + " > " + to);
    }
    T[] copy = ObjectArrays.newArray(original, newLength);
    for (int i = from, j = 0; i < to; i++, j++) {
      copy[j] = original[i];
      original[i] = null;
    }
    return copy;
  }

  public static <T> T[] append(final T[] array, final T value) {
    T[] result = java.util.Arrays.copyOf(array, array.length + 1);
    result[array.length] = value;
    return result;
  }

  @GwtIncompatible
  public static <T> T[] preppend(final T[] array, final T value) {
    Class<? extends Object[]> aClass = array.getClass();
    T[] copy = ((Object) aClass == (Object) Object[].class)
            ? (T[]) new Object[array.length + 1]
            : (T[]) Array.newInstance(aClass.getComponentType(), array.length + 1);
    System.arraycopy(array, 0, copy, 1, array.length);

    copy[0] = value;
    return copy;
  }

  @GwtIncompatible
  public static <T> T[] preppend(final T[] array, final T... values) {
    Class<? extends Object[]> aClass = array.getClass();
    T[] copy = ((Object) aClass == (Object) Object[].class)
            ? (T[]) new Object[array.length + values.length]
            : (T[]) Array.newInstance(aClass.getComponentType(), array.length + values.length);
    System.arraycopy(array, 0, copy, values.length, array.length);
    System.arraycopy(values, 0, copy, 0, values.length);
    return copy;
  }

  public static <T> T[] append(final T[] array, final T... values) {
    if (values.length == 0) {
      return array;
    }
    T[] result = java.util.Arrays.copyOf(array, array.length + values.length);
    System.arraycopy(values, 0, result, array.length, values.length);
    return result;
  }


  public static <T> T[] concat(final T[]... arrays) {
    if (arrays.length < 2) {
      throw new IllegalArgumentException("You should concatenate at least 2 arrays: "
              + arrays.length);
    }
    int newLength = 0;
    for (T[] arr : arrays) {
      newLength += arr.length;
    }
    T[] result = ObjectArrays.newArray(arrays[0], newLength);
    int destIdx = 0;
    for (T[] arr : arrays) {
      System.arraycopy(arr, 0, result, destIdx, arr.length);
      destIdx += arr.length;
    }
    return result;
  }

  public static byte[] concat(final  byte[]... arrays) {
    if (arrays.length < 2) {
      throw new IllegalArgumentException("You should concatenate at least 2 arrays: "
              + arrays.length);
    }
    int newLength = 0;
    for (byte[] arr : arrays) {
      newLength += arr.length;
    }
    byte[] result = new byte[newLength];
    int destIdx = 0;
    for (byte[] arr : arrays) {
      System.arraycopy(arr, 0, result, destIdx, arr.length);
      destIdx += arr.length;
    }
    return result;
  }

  public static <T> int indexOf(final T[] array, final T content) {
    int result = -1;
    for (int i = 0; i < array.length; i++) {
      if (array[i].equals(content)) {
        return i;
      }
    }
    return result;
  }

  /**
   * implementation which significantly faster for large arrays (> 500).
   */
  @Beta
  public static void fill(final byte[] array, final int startIdx, final int endIdx, final byte value) {
    int len = endIdx - startIdx;
    if (len > 0) {
      if (endIdx > array.length || startIdx < 0) {
        throw new IllegalArgumentException("Illegal range from " + startIdx + " to " + endIdx);
      }
      if (len <= ARR_CPY_THR) {
        for (int i = startIdx; i < endIdx; i++) {
          array[i] = value;
        }
      } else {
        int fillEnd = startIdx + ARR_CPY_THR;
        for (int i = startIdx; i < fillEnd; i++) {
          array[i] = value;
        }
        array[startIdx] = value;
        for (int i = ARR_CPY_THR; i < len; i += i) {
          int lmi = len - i;
          int from = startIdx + i;
          if (lmi < i) {
            if (lmi < ARR_CPY_THR) {
              int xe = from + lmi;
              for (int j = from; j < xe; j++) {
                array[j] = value;
              }
            } else {
              System.arraycopy(array, startIdx, array, from, lmi);
            }
          } else {
            System.arraycopy(array, startIdx, array, from, i);
          }
        }
      }
    } else if (len < 0) {
      throw new IllegalArgumentException("Illegal range from " + startIdx + " to " + endIdx);
    }
  }


  /**
   * implementation which is significantly faster for large arrays (> 500).
   * Bechmark results:
   * Benchmark                                      Mode Cnt Score            Error           Units
   * ArraysBenchmark.testSpf4jFillSmall(10)         thrpt 10 1048892782.375   ± 29976629.818  ops/s
   * ArraysBenchmark.testjdkFillSmall(10)           thrpt 10 1046330835.509   ± 47577260.717  ops/s
   * ArraysBenchmark.testSpf4jFillMedium(100)       thrpt 10 123724912.161    ± 4049077.779   ops/s
   * ArraysBenchmark.testjdkFillMedium(100)         thrpt 10 124143139.498    ± 2044760.427   ops/s
   * ArraysBenchmark.testSpf4jFillLarge(1000)       thrpt 10 20335282.192     ± 592359.181    ops/s
   * ArraysBenchmark.testjdkFillLarge(1000)         thrpt 10 12661975.522     ± 170457.046    ops/s
   * ArraysBenchmark.testSpf4jFillVeryLarge(10000)  thrpt 10 2767351.098      ± 74871.147     ops/s
   * ArraysBenchmark.testjdkFillVeryLarge(10000     thrpt 10 1045099.669      ± 30044.505     ops/s
   *
   */
  @Beta
  public static <T> void fill(final T[] array, final int startIdx, final int endIdx, final T value) {
    int len = endIdx - startIdx;
    if (len > 0) {
      if (endIdx > array.length || startIdx < 0) {
        throw new IllegalArgumentException("Illegal range from " + startIdx + " to " + endIdx);
      }
      if (len <= ARR_CPY_THR) {
        for (int i = startIdx; i < endIdx; i++) {
          array[i] = value;
        }
      } else {
        int fillEnd = startIdx + ARR_CPY_THR;
        for (int i = startIdx; i < fillEnd; i++) {
          array[i] = value;
        }
        array[startIdx] = value;
        for (int i = ARR_CPY_THR; i < len; i += i) {
          int lmi = len - i;
          int from = startIdx + i;
          if (lmi < i) {
            if (lmi < ARR_CPY_THR) {
              int xe = from + lmi;
              for (int j = from; j < xe; j++) {
                array[j] = value;
              }
            } else {
              System.arraycopy(array, startIdx, array, from, lmi);
            }
          } else {
            System.arraycopy(array, startIdx, array, from, i);
          }
        }
      }
    } else if (len < 0) {
      throw new IllegalArgumentException("Illegal range from " + startIdx + " to " + endIdx);
    }
  }

  @Beta
  public static void fill(final char[] array, final int startIdx, final int endIdx, final char value) {
    int len = endIdx - startIdx;
    if (len > 0) {
      if (endIdx > array.length || startIdx < 0) {
        throw new IllegalArgumentException("Illegal range from " + startIdx + " to " + endIdx);
      }
      if (len <= ARR_CPY_THR) {
        for (int i = startIdx; i < endIdx; i++) {
          array[i] = value;
        }
      } else {
        int fillEnd = startIdx + ARR_CPY_THR;
        for (int i = startIdx; i < fillEnd; i++) {
          array[i] = value;
        }
        array[startIdx] = value;
        for (int i = ARR_CPY_THR; i < len; i += i) {
          int lmi = len - i;
          int from = startIdx + i;
          if (lmi < i) {
            if (lmi < ARR_CPY_THR) {
              int xe = from + lmi;
              for (int j = from; j < xe; j++) {
                array[j] = value;
              }
            } else {
              System.arraycopy(array, startIdx, array, from, lmi);
            }
          } else {
            System.arraycopy(array, startIdx, array, from, i);
          }
        }
      }
    } else if (len < 0) {
      throw new IllegalArgumentException("Illegal range from " + startIdx + " to " + endIdx);
    }
  }

  @Beta
  public static void fill(final int[] array, final int startIdx, final int endIdx, final int value) {
    int len = endIdx - startIdx;
    if (len > 0) {
      if (endIdx > array.length || startIdx < 0) {
        throw new IllegalArgumentException("Illegal range from " + startIdx + " to " + endIdx);
      }
      if (len <= ARR_CPY_THR) {
        for (int i = startIdx; i < endIdx; i++) {
          array[i] = value;
        }
      } else {
        int fillEnd = startIdx + ARR_CPY_THR;
        for (int i = startIdx; i < fillEnd; i++) {
          array[i] = value;
        }
        array[startIdx] = value;
        for (int i = ARR_CPY_THR; i < len; i += i) {
          int lmi = len - i;
          int from = startIdx + i;
          if (lmi < i) {
            if (lmi < ARR_CPY_THR) {
              int xe = from + lmi;
              for (int j = from; j < xe; j++) {
                array[j] = value;
              }
            } else {
              System.arraycopy(array, startIdx, array, from, lmi);
            }
          } else {
            System.arraycopy(array, startIdx, array, from, i);
          }
        }
      }
    } else if (len < 0) {
      throw new IllegalArgumentException("Illegal range from " + startIdx + " to " + endIdx);
    }
  }

  @Beta
  public static void fill(final long[] array, final int startIdx, final int endIdx, final long value) {
    int len = endIdx - startIdx;
    if (len > 0) {
      if (endIdx > array.length || startIdx < 0) {
        throw new IllegalArgumentException("Illegal range from " + startIdx + " to " + endIdx);
      }
      if (len <= ARR_CPY_THR) {
        for (int i = startIdx; i < endIdx; i++) {
          array[i] = value;
        }
      } else {
        int fillEnd = startIdx + ARR_CPY_THR;
        for (int i = startIdx; i < fillEnd; i++) {
          array[i] = value;
        }
        array[startIdx] = value;
        for (int i = ARR_CPY_THR; i < len; i += i) {
          int lmi = len - i;
          int from = startIdx + i;
          if (lmi < i) {
            if (lmi < ARR_CPY_THR) {
              int xe = from + lmi;
              for (int j = from; j < xe; j++) {
                array[j] = value;
              }
            } else {
              System.arraycopy(array, startIdx, array, from, lmi);
            }
          } else {
            System.arraycopy(array, startIdx, array, from, i);
          }
        }
      }
    } else if (len < 0) {
      throw new IllegalArgumentException("Illegal range from " + startIdx + " to " + endIdx);
    }
  }

  @Beta
  public static void fill(final double[] array, final int startIdx, final int endIdx, final double value) {
    int len = endIdx - startIdx;
    if (len > 0) {
      if (endIdx > array.length || startIdx < 0) {
        throw new IllegalArgumentException("Illegal range from " + startIdx + " to " + endIdx);
      }
      if (len <= ARR_CPY_THR) {
        for (int i = startIdx; i < endIdx; i++) {
          array[i] = value;
        }
      } else {
        int fillEnd = startIdx + ARR_CPY_THR;
        for (int i = startIdx; i < fillEnd; i++) {
          array[i] = value;
        }
        array[startIdx] = value;
        for (int i = ARR_CPY_THR; i < len; i += i) {
          int lmi = len - i;
          int from = startIdx + i;
          if (lmi < i) {
            if (lmi < ARR_CPY_THR) {
              int xe = from + lmi;
              for (int j = from; j < xe; j++) {
                array[j] = value;
              }
            } else {
              System.arraycopy(array, startIdx, array, from, lmi);
            }
          } else {
            System.arraycopy(array, startIdx, array, from, i);
          }
        }
      }
    } else if (len < 0) {
      throw new IllegalArgumentException("Illegal range from " + startIdx + " to " + endIdx);
    }
  }

  public static byte[] charsToBytes(final char[] chars) {
    byte[] bytes = new byte[chars.length * 2];
    for (int i = 0, k = 0; i < chars.length; i++, k += 2) {
      char v = chars[i];
      bytes[k] = (byte) (0xff & (v >> 8));
      bytes[k + 1] = (byte) (0xff & (v));
    }
    return bytes;
  }

  public static char[] bytesToChars(final byte[] bytes) {
    char[] chars = new char[bytes.length / 2];
    for (int i = 0, k = 0; i < chars.length; i++, k += 2) {
      char v = (char) (((0xff & (bytes[k])) << 8) | (0xff & bytes[k + 1]));
      chars[i] = v;
    }
    return chars;
  }

}
