/*
 * Copyright 2000-2009 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 git4idea.commands;

import com.intellij.execution.process.ProcessOutputTypes;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Key;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.vcs.VcsException;
import com.intellij.openapi.vfs.VirtualFile;
import git4idea.i18n.GitBundle;
import org.jetbrains.annotations.NotNull;

import java.io.File;

/**
 * Simple Git handler that accumulates stdout and stderr and has nothing on stdin.
 * The handler executes commands synchronously with cancellable progress indicator.
 * <p/>
 * The class also includes a number of static utility methods that represent some
 * simple commands.
 */
public class GitSimpleHandler extends GitTextHandler {

  public static final String DURING_EXECUTING_ERROR_MESSAGE = "during executing";

  /**
   * Stderr output
   */
  private final StringBuilder myStderr = new StringBuilder();
  /**
   * Reminder of the last stderr line
   */
  private final StringBuilder myStderrLine = new StringBuilder();
  /**
   * Stdout output
   */
  private final StringBuilder myStdout = new StringBuilder();
  /**
   * Reminder of the last stdout line
   */
  private final StringBuilder myStdoutLine = new StringBuilder();

  /**
   * A constructor
   *
   * @param project   a project
   * @param directory a process directory
   * @param command   a command to execute
   */
  @SuppressWarnings({"WeakerAccess"})
  public GitSimpleHandler(@NotNull Project project, @NotNull File directory, @NotNull GitCommand command) {
    super(project, directory, command);
  }

  /**
   * A constructor
   *
   * @param project   a project
   * @param directory a process directory
   * @param command   a command to execute
   */
  @SuppressWarnings({"WeakerAccess"})
  public GitSimpleHandler(@NotNull final Project project, @NotNull final VirtualFile directory, @NotNull final GitCommand command) {
    super(project, directory, command);
  }

  /**
   * {@inheritDoc}
   */
  protected void processTerminated(final int exitCode) {
    if (myVcs == null) { return; }
    String stdout = myStdoutLine.toString();
    String stderr = myStderrLine.toString();
    if (!isStdoutSuppressed() && !StringUtil.isEmptyOrSpaces(stdout)) {
      myVcs.showMessages(stdout);
      LOG.info(stdout.trim());
      myStdoutLine.setLength(0);
    }
    else if (!isStderrSuppressed() && !StringUtil.isEmptyOrSpaces(stderr)) {
      myVcs.showErrorMessages(stderr);
      LOG.info(stderr.trim());
      myStderrLine.setLength(0);
    }
    else {
      LOG.debug(stderr.trim());
      OUTPUT_LOG.debug(stdout.trim());
    }
  }

  /**
   * For silent handlers, print out everything
   */
  public void unsilence() {
    if (myVcs == null) { return; }
    myVcs.showCommandLine(printableCommandLine());
    if (myStderr.length() != 0) {
      myVcs.showErrorMessages(myStderr.toString());
    }
    if (myStdout.length() != 0) {
      myVcs.showMessages(myStdout.toString());
    }
  }

  /**
   * {@inheritDoc}
   */
  protected void onTextAvailable(final String text, final Key outputType) {
    final StringBuilder entire;
    final StringBuilder lineRest;
    final boolean suppressed;
    if (ProcessOutputTypes.STDOUT == outputType) {
      entire = myStdout;
      lineRest = myStdoutLine;
      suppressed = isStdoutSuppressed();
    }
    else if (ProcessOutputTypes.STDERR == outputType) {
      entire = myStderr;
      lineRest = myStderrLine;
      suppressed = isStderrSuppressed();
    }
    else {
      return;
    }
    entire.append(text);
    if (myVcs == null || (suppressed && !LOG.isDebugEnabled())) {
      return;
    }
    int last = lineRest.length() > 0 ? lineRest.charAt(lineRest.length() - 1) : -1;
    int start = 0;
    for (int i = 0; i < text.length(); i++) {
      char ch = text.charAt(i);
      if (last == '\n' || last == '\r') {
        int savedPos;
        if ((ch == '\n' || ch == '\r') && ch != last) {
          savedPos = i - 1;
        }
        else {
          savedPos = i;
        }
        if (last != '\r' || savedPos != i) {
          String line;
          if (lineRest.length() == 0) {
            line = lineRest.append(text.substring(start, savedPos)).toString();
            lineRest.setLength(0);
          }
          else {
            line = text.substring(start, savedPos);
          }
          if (!StringUtil.isEmptyOrSpaces(line)) {
            if (!suppressed) {
              LOG.info(line.trim());
              if (ProcessOutputTypes.STDOUT == outputType) {
                myVcs.showMessages(line);
              }
              else if (ProcessOutputTypes.STDERR == outputType) {
                myVcs.showErrorMessages(line);
              }
            }
            else {
              LOG.debug(line.trim());
            }
          }
        }
        start = savedPos;
      }
      last = ch;
    }
    if (start != text.length()) {
      lineRest.append(text.substring(start));
    }
  }

  /**
   * @return stderr contents
   */
  public String getStderr() {
    return myStderr.toString();
  }

  /**
   * @return stdout contents
   */
  public String getStdout() {
    return myStdout.toString();
  }

  /**
   * Execute without UI. If UI interactions are required (for example SSH popups or progress dialog), use {@link GitHandlerUtil} methods.
   *
   * @return a value if process was successful
   * @throws VcsException exception if process failed to start.
   */
  public String run() throws VcsException {
    if (isRemote()) {
      throw new IllegalStateException("Commands that require remote access could not be run using this method");
    }
    final VcsException[] ex = new VcsException[1];
    final String[] result = new String[1];
    addListener(new GitHandlerListener() {
      public void processTerminated(final int exitCode) {
        try {
          if (exitCode == 0 || isIgnoredErrorCode(exitCode)) {
            result[0] = getStdout();
          }
          else {
            String msg = getStderr();
            if (msg.length() == 0) {
              msg = getStdout();
            }
            if (msg.length() == 0) {
              msg = GitBundle.message("git.error.exit", exitCode);
            }
            ex[0] = new VcsException(msg);
          }
        }
        catch (Throwable t) {
          ex[0] = new VcsException(t.toString(), t);
        }
      }

      public void startFailed(final Throwable exception) {
        ex[0] = new VcsException("Process failed to start (" + myCommandLine.getCommandLineString() + "): " + exception.toString(), exception);
      }
    });
    runInCurrentThread(null);
    if (ex[0] != null) {
      throw new VcsException(ex[0].getMessage() + " " + DURING_EXECUTING_ERROR_MESSAGE + " " + printableCommandLine(), ex[0]);
    }
    if (result[0] == null) {
      throw new VcsException("The git command returned null: " + printableCommandLine());
    }
    return result[0];
  }
}
