package com.jetbrains.ther.run.graphics;

import com.intellij.openapi.Disposable;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.project.ProjectCoreUtil;
import com.intellij.openapi.util.Disposer;
import com.intellij.openapi.util.Ref;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.util.PathUtil;
import com.jetbrains.ther.debugger.data.TheRCommands;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.io.File;
import java.io.IOException;
import java.util.*;

import static com.jetbrains.ther.debugger.data.TheRFunctionConstants.SERVICE_FUNCTION_PREFIX;

public final class TheRGraphicsUtils {

  @NotNull
  private static final Logger LOGGER = Logger.getInstance(TheRGraphicsUtils.class);

  @NotNull
  private static final Map<String, TheRGraphicsState> GRAPHICS_STATES = new HashMap<String, TheRGraphicsState>();

  @NotNull
  private static final String DEVICE_LIB_FORMAT = "libtherplugin_device%s.so";

  @NotNull
  private static final String DEVICE_FUNCTION_NAME = SERVICE_FUNCTION_PREFIX + "device_init";

  @NotNull
  private static final String SETUP_DEVICE_COMMAND = TheRCommands.optionsCommand("device", DEVICE_FUNCTION_NAME);

  @NotNull
  private static final String LIB_DIR_NAME = "libs";

  @NotNull
  private static final String LIB_IS_NOT_FOUND = "Lib is not found [path: %s]";

  @NotNull
  private static final String LIB_IS_NOT_READABLE = "Lib is not readable [path: %s]";

  @NotNull
  private static final String PROJECT_DIR_IS_NOT_FOUND = "Project dir is not found [path: %s]";

  @NotNull
  private static final String SNAPSHOT_DIR_NAME = "snapshots";

  @NotNull
  private static final String SNAPSHOT_DIR_IS_NOT_FOUND = "Snapshot dir is not found [path: %s]";

  @NotNull
  private static final String SNAPSHOT_DIR_IS_FOUND = "Snapshot dir is found [path: %s]";

  @NotNull
  private static final String SNAPSHOT_DIR_IS_NOT_WRITABLE = "Snapshot dir is not writable [path: %s]";

  @NotNull
  private static final String SNAPSHOT_DIR_HAS_BEEN_CREATED = "Snapshot dir has been created [path: %s]";

  @NotNull
  public static List<String> calculateInitCommands(@NotNull final Project project, final boolean is64Bit) {
    final String libPath = getLibPath(
      String.format(DEVICE_LIB_FORMAT, is64Bit ? "64" : "32")
    );

    if (libPath != null) {
      final VirtualFile snapshotDir = getSnapshotDir(project);

      if (snapshotDir != null) {
        return Arrays.asList(
          TheRCommands.loadLibCommand(libPath),
          DEVICE_FUNCTION_NAME + " <- function() { .Call(\"" + DEVICE_FUNCTION_NAME + "\", \"" + snapshotDir.getPath() + "\") }",
          SETUP_DEVICE_COMMAND
        );
      }
    }

    return Collections.emptyList();
  }

  @NotNull
  public static TheRGraphicsState getGraphicsState(@NotNull final Project project) {
    final VirtualFile snapshotDir = getSnapshotDir(project);

    if (snapshotDir == null) {
      return new TheREmptyGraphicsState();
    }

    final String snapshotDirPath = snapshotDir.getPath();

    if (!GRAPHICS_STATES.containsKey(snapshotDirPath)) {
      final TheRGraphicsStateImpl state = new TheRGraphicsStateImpl(snapshotDir);

      Disposer.register(project, state);
      Disposer.register(
        state,
        new Disposable() {
          @Override
          public void dispose() {
            GRAPHICS_STATES.remove(snapshotDirPath);
          }
        }
      );

      GRAPHICS_STATES.put(snapshotDirPath, state);
    }

    return GRAPHICS_STATES.get(snapshotDirPath);
  }

  @Nullable
  private static String getLibPath(@NotNull final String libName) {
    final File pluginDir = new File(PathUtil.getJarPathForClass(TheRGraphicsUtils.class));
    final File libDir = new File(pluginDir, LIB_DIR_NAME);
    final File libFile = new File(libDir, libName);
    final String absolutePath = libFile.getAbsolutePath();

    if (!libFile.exists()) {
      LOGGER.warn(
        String.format(LIB_IS_NOT_FOUND, absolutePath)
      );

      return null;
    }

    if (!libFile.canRead()) {
      LOGGER.warn(
        String.format(LIB_IS_NOT_READABLE, absolutePath)
      );

      return null;
    }

    return absolutePath;
  }

  @Nullable
  private static VirtualFile getSnapshotDir(@NotNull final Project project) {
    final String projectDirName = ProjectCoreUtil.DIRECTORY_BASED_PROJECT_DIR;
    final VirtualFile dotIdeaDir = project.getBaseDir().findChild(projectDirName);

    if (dotIdeaDir != null) {
      return getSnapshotDir(dotIdeaDir);
    }
    else {
      LOGGER.warn(
        String.format(
          PROJECT_DIR_IS_NOT_FOUND,
          new File(project.getBasePath(), projectDirName).getAbsolutePath()
        )
      );

      return null;
    }
  }

  @Nullable
  private static VirtualFile getSnapshotDir(@NotNull final VirtualFile dotIdeaDir) {
    final VirtualFile snapshotDir = dotIdeaDir.findChild(SNAPSHOT_DIR_NAME);

    if (snapshotDir != null) {
      return checkSnapshotDir(snapshotDir);
    }
    else {
      LOGGER.info(
        String.format(
          SNAPSHOT_DIR_IS_NOT_FOUND,
          new File(dotIdeaDir.getPath(), SNAPSHOT_DIR_NAME).getAbsolutePath()
        )
      );

      return createSnapshotDir(dotIdeaDir);
    }
  }

  @Nullable
  private static VirtualFile checkSnapshotDir(@NotNull final VirtualFile snapshotDir) {
    final String snapshotDirPath = snapshotDir.getPath();

    if (snapshotDir.isWritable()) {
      LOGGER.info(
        String.format(SNAPSHOT_DIR_IS_FOUND, snapshotDirPath)
      );

      return snapshotDir;
    }
    else {
      LOGGER.warn(
        String.format(SNAPSHOT_DIR_IS_NOT_WRITABLE, snapshotDirPath)
      );

      return null;
    }
  }

  @Nullable
  private static VirtualFile createSnapshotDir(@NotNull final VirtualFile dotIdeaDir) {
    final Ref<VirtualFile> resultRef = new Ref<VirtualFile>(null);

    ApplicationManager.getApplication().runWriteAction(
      new Runnable() {
        @Override
        public void run() {
          try {
            resultRef.set(
              dotIdeaDir.createChildDirectory(new TheRGraphicsUtils(), SNAPSHOT_DIR_NAME)
            );

            LOGGER.info(
              String.format(SNAPSHOT_DIR_HAS_BEEN_CREATED, resultRef.get().getPath())
            );
          }
          catch (final IOException e) {
            LOGGER.error(e);
          }
        }
      }
    );

    return resultRef.get();
  }
}

@@ -393,58 +393,115 @@
 api.
-vfs.VirtualFile;%0Aimport com.intellij.util.PathUtil
+util.io.FileUtil;%0Aimport com.intellij.openapi.vfs.VirtualFile;%0Aimport com.jetbrains.ther.TheRHelpersLocator
 ;%0Aim
@@ -1392,73 +1392,8 @@
 );%0A%0A
-  @NotNull%0A  private static final String LIB_DIR_NAME = %22libs%22;%0A%0A
   @N
@@ -3761,185 +3761,51 @@
 ile 
-pluginDir = new File(PathUtil.getJarPathForClass(TheRGraphicsUtils.class));%0A    final File libDir = new File(pluginDir, LIB_DIR_NAME);%0A    final File libFile = new File(libDir, 
+libFile = TheRHelpersLocator.getHelperFile(
 libN
@@ -3842,16 +3842,49 @@
 ePath = 
+FileUtil.toSystemIndependentName(
 libFile.
@@ -3896,24 +3896,25 @@
 solutePath()
+)
 ;%0A%0A    if (!

use helpers locator to calculate path to lib