/*
 * Copyright 2003-2011 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 jetbrains.mps.idea.core.make;

import com.intellij.compiler.CompilerConfiguration;
import com.intellij.compiler.CompilerWorkspaceConfiguration;
import com.intellij.compiler.impl.CompilerUtil;
import com.intellij.compiler.server.CustomBuilderMessageHandler;
import com.intellij.openapi.compiler.CompileScope;
import com.intellij.openapi.compiler.CompilerManager;
import com.intellij.openapi.compiler.CompilerMessageCategory;
import com.intellij.openapi.compiler.CompilerPaths;
import com.intellij.openapi.components.ProjectComponent;
import com.intellij.openapi.project.Project;
import jetbrains.mps.fileTypes.MPSFileTypeFactory;
import jetbrains.mps.ide.project.ProjectHelper;
import jetbrains.mps.idea.core.MPSBundle;
import jetbrains.mps.idea.core.module.CachedRepositoryData;
import jetbrains.mps.library.LibraryInitializer;
import jetbrains.mps.project.MPSExtentions;
import jetbrains.mps.util.io.ModelOutputStream;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.jps.incremental.messages.BuildMessage.Kind;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.atomic.AtomicReference;

/**
 * evgeny, 11/21/11
 */
public class MPSCompilerComponent implements ProjectComponent {
  private final Project myProject;
  private final CompilerManager compilerManager;
  private final CompilerConfiguration compilerConfiguration;

  public MPSCompilerComponent(Project project, CompilerManager compilerManager, CompilerConfiguration compilerConfiguration) {
    myProject = project;
    this.compilerManager = compilerManager;
    this.compilerConfiguration = compilerConfiguration;
  }

  @Override
  public void projectOpened() {
    final List<String> errorMessages = new ArrayList<>();

    myProject.getMessageBus().connect().subscribe(CustomBuilderMessageHandler.TOPIC, new RefreshFilesCompilationStatusListener());
    myProject.getMessageBus().connect().subscribe(CustomBuilderMessageHandler.TOPIC, new NavigateToNodesWithErrors(errorMessages));

    compilerManager.addCompilableFileType(MPSFileTypeFactory.MPS_FILE_TYPE);
    compilerManager.addCompilableFileType(MPSFileTypeFactory.MPS_ROOT_FILE_TYPE);
    for (String ext : Arrays.asList(MPSExtentions.MODEL, MPSExtentions.MODEL_ROOT, MPSExtentions.MODEL_HEADER)) {
      if (compilerConfiguration.isResourceFile("." + ext)) {
        String negatedPattern = "!*." + ext;
        compilerConfiguration.addResourceFilePattern(negatedPattern);
      }
    }

    compilerManager.addBeforeTask(context -> {
      final CompileScope compileScope = context.getCompileScope();
      if (compileScope == null) return true;

      final File repositoryCache = new File(CompilerPaths.getCompilerSystemDirectory(myProject), "mps_repository.dat");
      final long start = System.nanoTime();
      ProjectHelper.fromIdeaProject(myProject).getModelAccess().runReadAction(() -> {
        CachedRepositoryData cachedRepositoryData = MPSRepositoryUtil.buildData(LibraryInitializer.getInstance().getModuleHandles());
        ModelOutputStream mos = null;
        try {
          mos = new ModelOutputStream(new FileOutputStream(repositoryCache));
          cachedRepositoryData.save(mos);
          compileScope.putUserData(MPSMakeConstants.MPS_REPOSITORY, repositoryCache.getPath());
        } catch (IOException e) {
          context.addMessage(CompilerMessageCategory.INFORMATION, MPSBundle.message("mps.compiler.component.message.slow"), null, 0, 0);
        } finally {
          jetbrains.mps.util.FileUtil.closeFileSafe(mos);
        }
      });
      long result = (System.nanoTime() - start) / 1000000;

      if (CompilerWorkspaceConfiguration.getInstance(myProject).COMPILER_PROCESS_ADDITIONAL_VM_OPTIONS.contains(MPSBundle.message("mps.compiler.component.debug.flag"))) {
        context.addMessage(CompilerMessageCategory.INFORMATION, String.format(MPSBundle.message("mps.compiler.component.message.cache.saved"), result), null, 0, 0);
      }
      return true;
    });

    compilerManager.addAfterTask(context -> {
      for (String errorMessage : errorMessages) {
        ModelNodeNavigatable navigatable = ModelNodeNavigatable.extractNavigatable(errorMessage, context.getProject(), null);
        context.addMessage(CompilerMessageCategory.ERROR, errorMessage, null, -1, -1, navigatable);
      }
      boolean noErrors = errorMessages.isEmpty();
      errorMessages.clear();
      return noErrors;
    });
  }

  @Override
  public void projectClosed() {
  }

  @Override
  public void initComponent() {
  }

  @Override
  public void disposeComponent() {
  }

  @Override
  @NotNull
  public String getComponentName() {
    return "MPS Compiler Component";
  }

  private class RefreshFilesCompilationStatusListener implements CustomBuilderMessageHandler {
    private final AtomicReference<List<File>>
      myAffectedFiles = new AtomicReference<>(new ArrayList<>());

    @Override
    public void messageReceived(String builderId, String messageType, String messageText) {
      if (MPSMakeConstants.BUILDER_ID.equals(builderId)) {

        if (messageType.equals(MPSCustomMessages.MSG_GENERATED)) {
          myAffectedFiles.get().add(new File(messageText));

        } else if (messageType.equals(MPSCustomMessages.MSG_REFRESH)) {
          final List<File> generatedFiles = myAffectedFiles.getAndSet(new ArrayList<>());
          if (myProject.isDisposed() || generatedFiles.isEmpty()) {
            return;
          }

          // refresh affected files
          CompilerUtil.refreshIOFiles(generatedFiles);
        }
      }
    }
  }

  private class NavigateToNodesWithErrors implements CustomBuilderMessageHandler {
    private final List<String> myErrorMessages;

    public NavigateToNodesWithErrors(List<String> errorMessages) {
      myErrorMessages = errorMessages;
    }

    @Override
    public void messageReceived(String builderId, String messageType, final String messageText) {
      if (MPSMakeConstants.BUILDER_ID.equals(builderId) && (Kind.ERROR.toString().equals(messageType))) {
        myErrorMessages.add(messageText);
      }
    }
  }
}

