// Copyright 2006-2012 AdvancedTools. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.advancedtools.cpp.facade;

import com.advancedtools.cpp.CppSupportLoader;
import com.advancedtools.cpp.build.BaseBuildHandler;
import com.advancedtools.cpp.build.BasicFormatFilter;
import com.advancedtools.cpp.build.BuildState;
import com.advancedtools.cpp.build.BuildTarget;
import com.advancedtools.cpp.sdk.CppModuleType;
import com.advancedtools.cpp.sdk.CppSdkType;
import com.intellij.openapi.compiler.*;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.module.ModuleType;
import com.intellij.openapi.progress.ProgressIndicator;
import com.intellij.openapi.progress.ProgressManager;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.projectRoots.Sdk;
import com.intellij.openapi.roots.ModuleRootManager;
import com.intellij.openapi.ui.Messages;
import com.intellij.openapi.vfs.LocalFileSystem;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.util.Computable;
import org.jetbrains.annotations.NotNull;

import java.io.*;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;


public class CppCompiler implements Validator {
  @NotNull
  public ProcessingItem[] getProcessingItems(final CompileContext compileContext) {
    final List<ProcessingItem> processingItems = new ArrayList<ProcessingItem>();
    boolean doneSave = false;

    Module[] affectedModules = ApplicationManager.getApplication().runReadAction(new Computable<Module[]>() {
      public Module[] compute() {
        return compileContext.getCompileScope().getAffectedModules();
      }
    });

    for(Module module: affectedModules) {
      Sdk sdk = ModuleRootManager.getInstance(module).getSdk();
      if (ModuleType.get(module) == CppModuleType.getInstance() ||
         (sdk != null && sdk.getSdkType() == CppSdkType.getInstance())) {
        processingItems.add(new MyProcessingItem(module));

        if (!doneSave) {
          BuildState.saveDocuments();
          doneSave = true;
        }

        VirtualFile moduleFile = module.getModuleFile();
        if (moduleFile == null) {
          BuildState.saveAll();
        }
      }
    }
    return processingItems.toArray(new ProcessingItem[processingItems.size()]);
  }

  public ProcessingItem[] process(final CompileContext compileContext, ProcessingItem[] processingItems) {
    if (processingItems.length == 0) return processingItems;
    Project project = ((MyProcessingItem)processingItems[0]).myModule.getProject();
    CppSupportLoader loader = CppSupportLoader.getInstance(project);
    String projectFilePath = loader.getProjectFile();
    VirtualFile projectFile = projectFilePath != null ? LocalFileSystem.getInstance().findFileByPath(projectFilePath):null;

    if (projectFile != null) {
      final BaseBuildHandler buildHandler = BaseBuildHandler.getBuildHandler(project, projectFile);

      if (buildHandler != null) {
        String buildAction = compileContext.isMake() ? loader.getLastBuildAction() : "rebuild";

        BuildTarget buildTarget = new BuildTarget(loader.getActiveConfiguration(), buildAction, loader.getAdditionalCommandLineBuildParameters());
        final BuildState buildState = buildHandler.createBuildState(buildTarget);

        if (buildState != null) {
          try {
            buildState.start();
            final BasicFormatFilter filter = (BasicFormatFilter) buildHandler.getOutputFormatFilter();

            Future<?> future = ApplicationManager.getApplication().executeOnPooledThread((Runnable) new CompilerStreamReader(
              buildState.getProcess().getErrorStream(), compileContext
            ) {
              protected void onInputLine(String s) {
                if (filter != null) {
                  BasicFormatFilter.BasicFormatResult result = (BasicFormatFilter.BasicFormatResult) filter.applyFilter(s, s.length());

                  if (result != null) {
                    CompilerMessageCategory category = result.isError() ? CompilerMessageCategory.ERROR : CompilerMessageCategory.WARNING;
                    String message = s.substring(result.highlightEndOffset + 1);
                    compileContext.addMessage(category, message, result.file.getUrl(), result.line, result.column);
                    return;
                  }
                }
                compileContext.addMessage(CompilerMessageCategory.WARNING, s, null, 0, 0);
              }
            });
            Future<?> future2 = ApplicationManager.getApplication().executeOnPooledThread((Runnable) new CompilerStreamReader(
              buildState.getProcess().getInputStream(), compileContext
            ) {
              final ProgressIndicator pi = ProgressManager.getInstance().getProgressIndicator();

              protected void onInputLine(String s) {
                if (pi != null) pi.setText2(s);
                compileContext.addMessage(CompilerMessageCategory.INFORMATION, s, null, 0, 0);
              }
            });
            future.get();
            future2.get();
          } catch (IOException e) {
            compileContext.addMessage(CompilerMessageCategory.ERROR, e.getLocalizedMessage(), null, 0, 0);
          } catch (InterruptedException ex) {}
          catch (ExecutionException ex) {}
        }
      }
    }
    
    return processingItems;
  }

  @NotNull
  public String getDescription() {
    return "C/C++ compiler";
  }

  public boolean validateConfiguration(CompileScope compileScope) {
    EnvironmentFacade facade = EnvironmentFacade.getInstance();

    for(Module module:compileScope.getAffectedModules()) {
      if (ModuleType.get(module) == CppModuleType.getInstance()) {
        Sdk sdk = ModuleRootManager.getInstance(module).getSdk();

        if (!(sdk.getSdkType() == CppSdkType.getInstance())) {
          Messages.showMessageDialog(module.getProject(), "C/Cpp module type is not configured", "C/C++ compiler problem", Messages.getErrorIcon());
          return false;
        }
      }
    }
    return true;
  }

  // IDEA 8
  public ValidityState createValidityState(DataInput dataInput) throws IOException {
    return new MyValidityState();
  }

  public ValidityState createValidityState(DataInputStream dataInputStream) throws IOException {
    return new MyValidityState();
  }

  private static class MyProcessingItem implements ProcessingItem {
    private Module myModule;

    public MyProcessingItem(Module module) {
      myModule = module;
    }

    @NotNull
    public VirtualFile getFile() {
      return myModule.getModuleFile();
    }

    public ValidityState getValidityState() {
      return new MyValidityState();
    }
  }

  private static class MyValidityState implements ValidityState {
    public boolean equalsTo(ValidityState validityState) {
      return validityState == this;
    }

    public void save(DataOutputStream dataOutputStream) throws IOException {
    }

    // IDEA 8
    public void save(DataOutput dataOutput) throws IOException {
    }
  }

  private static abstract class CompilerStreamReader implements Runnable {
    private final InputStream stream;
    protected final CompileContext compileContext;

    public CompilerStreamReader(InputStream _stream, CompileContext _compileContext) {
      stream = _stream;
      compileContext = _compileContext;
    }

    public void run() {
      try {
        LineNumberReader reader = new LineNumberReader(new InputStreamReader(new BufferedInputStream(stream)));
        while (true) {
          String s = reader.readLine();
          if (s == null) break;
          onInputLine(s);
        }
      } catch (IOException ex) {
        compileContext.addMessage(CompilerMessageCategory.ERROR, ex.getLocalizedMessage(), null, 0, 0);
      } finally {
        try {
          stream.close();
        } catch (IOException ex) {}
      }
    }

    protected abstract void onInputLine(String s);
  }
}