/*
 * 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.openapi.vfs.newvfs.impl;

import com.intellij.openapi.util.SystemInfo;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.vfs.newvfs.persistent.FSRecords;
import com.intellij.util.containers.IntObjectLinkedMap;
import com.intellij.util.IntSLRUCache;
import com.intellij.util.io.IOUtil;
import com.intellij.util.io.PersistentStringEnumerator;
import com.intellij.util.text.StringFactory;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.util.concurrent.locks.ReentrantReadWriteLock;

/**
 * @author peter
 */
public class FileNameCache {
  private static final PersistentStringEnumerator ourNames = FSRecords.getNames();
  @NonNls private static final String EMPTY = "";
  private static final IntSLRUCache<IntObjectLinkedMap.MapEntry<Object>> ourNameCache = new IntSLRUCache<IntObjectLinkedMap.MapEntry<Object>>(40000, 20000);
  private static final ReentrantReadWriteLock ourLock = new ReentrantReadWriteLock();

  public static int storeName(@NotNull String name) {
    final int idx = FSRecords.getNameId(name);
    cacheData(name, idx);
    return idx;
  }

  @NotNull
  private static IntObjectLinkedMap.MapEntry<Object> cacheData(String name, int id) {
    if (name == null) {
      ourNames.markCorrupted();
      throw new RuntimeException("VFS name enumerator corrupted");
    }

    Object rawName = convertToBytesIfAsciiString(name);
    IntObjectLinkedMap.MapEntry<Object> entry = new IntObjectLinkedMap.MapEntry<Object>(id, rawName);
    ourLock.writeLock().lock();
    try {
      return ourNameCache.cacheEntry(entry);
    }
    finally {
      ourLock.writeLock().unlock();
    }
  }

  private static Object convertToBytesIfAsciiString(@NotNull String name) {
    int length = name.length();
    if (length == 0) return EMPTY;

    if (!IOUtil.isAscii(name)) {
      return name;
    }

    byte[] bytes = new byte[length];
    for (int i = 0; i < length; i++) {
      bytes[i] = (byte)name.charAt(i);
    }
    return bytes;
  }

  @NotNull
  private static IntObjectLinkedMap.MapEntry<Object> getEntry(int id) {
    boolean allowMutation = ourLock.writeLock().tryLock();
    if (!allowMutation) {
      ourLock.readLock().lock();
    }
    try {
      IntObjectLinkedMap.MapEntry<Object> entry = ourNameCache.getCachedEntry(id, allowMutation);
      if (entry != null) {
        return entry;
      }
    }
    finally {
      if (allowMutation) {
        ourLock.writeLock().unlock();
      } else {
        ourLock.readLock().unlock();
      }
    }

    return cacheData(FSRecords.getNameByNameId(id), id);
  }

  @NotNull
  public static String getVFileName(int nameId) {
    IntObjectLinkedMap.MapEntry<Object> entry = getEntry(nameId);
    Object name = entry.value;
    if (name instanceof String) {
      //noinspection StringEquality
      return (String)name;
    }

    byte[] bytes = (byte[])name;
    int length = bytes.length;
    char[] chars = new char[length];
    for (int i = 0; i < length; i++) {
      chars[i] = (char)bytes[i];
    }
    return StringFactory.createShared(chars);
  }

  static int compareNameTo(int nameId, @NotNull String name, boolean ignoreCase) {
    IntObjectLinkedMap.MapEntry<Object> entry = getEntry(nameId);
    Object rawName = entry.value;
    if (rawName instanceof String) {
      String thisName = getVFileName(nameId);
      return VirtualFileSystemEntry.compareNames(thisName, name, ignoreCase);
    }

    byte[] bytes = (byte[])rawName;
    int bytesLength = bytes.length;

    int d = bytesLength - name.length();
    if (d != 0) return d;

    return compareBytes(bytes, 0, name, 0, bytesLength, ignoreCase);
  }

  private static int compareBytes(@NotNull byte[] name1, int offset1, @NotNull String name2, int offset2, int len, boolean ignoreCase) {
    for (int i1 = offset1, i2=offset2; i1 < offset1 + len; i1++, i2++) {
      char c1 = (char)name1[i1];
      char c2 = name2.charAt(i2);
      int d = StringUtil.compare(c1, c2, ignoreCase);
      if (d != 0) return d;
    }
    return 0;
  }

  static char[] appendPathOnFileSystem(int nameId, @Nullable VirtualFileSystemEntry parent, int accumulatedPathLength, int[] positionRef) {
    IntObjectLinkedMap.MapEntry<Object> entry = getEntry(nameId);
    Object o = entry.value;
    int nameLength = o instanceof String ? ((String)o).length() : ((byte[])o).length;
    boolean appendSlash = SystemInfo.isWindows && parent == null && nameLength == 2 &&
                          (o instanceof String ? ((String)o).charAt(1) : (char)((byte[])o)[1]) == ':';

    char[] chars;
    if (parent != null) {
      chars = parent.appendPathOnFileSystem(accumulatedPathLength + 1 + nameLength, positionRef);
      if (positionRef[0] > 0 && chars[positionRef[0] - 1] != '/') {
        chars[positionRef[0]++] = '/';
      }
    }
    else {
      int rootPathLength = accumulatedPathLength + nameLength;
      if (appendSlash) ++rootPathLength;
      chars = new char[rootPathLength];
    }

    if (o instanceof String) {
      positionRef[0] = VirtualFileSystemEntry.copyString(chars, positionRef[0], (String)o);
    }
    else {
      byte[] bytes = (byte[])o;
      int pos = positionRef[0];
      //noinspection ForLoopReplaceableByForEach
      for (int i = 0, len = bytes.length; i < len; i++) {
        chars[pos++] = (char)bytes[i];
      }
      positionRef[0] = pos;
    }

    if (appendSlash) {
      chars[positionRef[0]++] = '/';
    }

    return chars;
  }

}

@@ -1160,67 +1160,8 @@
 e;%0A%0A
-import java.util.concurrent.locks.ReentrantReadWriteLock;%0A%0A
 /**%0A
@@ -1503,94 +1503,8 @@
 00);
-%0A  private static final ReentrantReadWriteLock ourLock = new ReentrantReadWriteLock();
 %0A%0A  
@@ -2042,43 +2042,35 @@
     
-ourLock.writeLock().lock();%0A    try
+synchronized (ourNameCache)
  %7B%0A 
@@ -2123,64 +2123,8 @@
   %7D%0A
-    finally %7B%0A      ourLock.writeLock().unlock();%0A    %7D%0A
   %7D%0A
@@ -2562,135 +2562,35 @@
     
-boolean allowMutation = ourLock.writeLock().tryLock();%0A    if (!allowMutation) %7B%0A      ourLock.readLock().lock();%0A    %7D%0A    try
+synchronized (ourNameCache)
  %7B%0A 
@@ -2672,23 +2672,8 @@
 y(id
-, allowMutation
 );%0A 
@@ -2737,153 +2737,8 @@
    %7D
-%0A    finally %7B%0A      if (allowMutation) %7B%0A        ourLock.writeLock().unlock();%0A      %7D else %7B%0A        ourLock.readLock().unlock();%0A      %7D%0A    %7D
 %0A%0A  

rollback FileNameCache optimization as it leads to (dead?)locks of unclear nature