/*
Copyright 2011-2016 Google Inc. All Rights Reserved.

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.google.security.zynamics.zylib.gui.zygraph.realizers.KeyBehaviours;

import com.google.security.zynamics.zylib.gui.zygraph.realizers.ECommentPlacement;
import com.google.security.zynamics.zylib.gui.zygraph.realizers.IZyEditableObject;
import com.google.security.zynamics.zylib.gui.zygraph.realizers.ZyLineContent;
import com.google.security.zynamics.zylib.gui.zygraph.realizers.KeyBehaviours.UndoHistroy.CUndoManager;

public class CReturnKeyBehavior extends CAbstractKeyBehavior {
  private int m_caretY = -1;

  private boolean m_wasUneditableSelection;

  public CReturnKeyBehavior(final CUndoManager undoManager) {
    super(undoManager);
  }

  @Override
  protected void initUndoHistory() {
    int x = getCaretEndPosX();
    int y = getCaretMouseReleasedY();

    final ZyLineContent lineContent = getLineContent(y);

    if (isComment(x, y)) {
      // Caret end was within a comment, when return was pressed

      IZyEditableObject lineObject = lineContent.getLineFragmentObjectAt(x);

      String text = "";

      if (lineObject.isCommentDelimiter()) {
        x = lineObject.getEnd();

        lineObject = lineContent.getLineFragmentObjectAt(x);
      }

      text = getMultiLineComment(y);

      udpateUndolist(getLabelContent(), lineContent.getLineObject().getPersistentModel(),
          lineObject, text, isAboveLineComment(y), isBehindLineComment(x, y), isLabelComment(y),
          getCaretStartPosX(), getCaretMousePressedX(), getCaretMousePressedY(), getCaretEndPosX(),
          getCaretMouseReleasedX(), getCaretMouseReleasedY());
    } else {
      // Caret was not within a comment. Note: Line fragments are always single lined

      final ZyLineContent nextModelLineContent = getNextModelLineContent(y);

      y = getNextModelLineIndex(y);

      String text = "";

      boolean isAboveLineComment = false;
      boolean isBehindLineComment = false;
      boolean isLabelComment = false;

      IZyEditableObject editableObject =
          nextModelLineContent == null ? getLabelContent().getModel() : nextModelLineContent
              .getLineObject();

      if ((x == lineContent.getText().length()) && (getCaretMouseReleasedY() != 0)) {
        // Caret is at the end of a non-comment line. A new behind line comment will be created

        editableObject = lineContent.getLineObject();

        isBehindLineComment = true;
      } else if ((nextModelLineContent != null) && !isLabelComment(y)) {
        // There is a next model line, but it's not the label comment

        if (isComment(0, y)) {
          text = getMultiLineComment(y);
        }
        isAboveLineComment = true;
      } else if ((nextModelLineContent != null) && isLabelComment(y)) {
        // There is a next model line content and it's the label comment

        text += getMultiLineComment(y);
        isLabelComment = true;
      } else {
        // There is no next model line content and there is still no label comment appended

        isLabelComment = true;
      }

      if (editableObject == null) {
        return;
      }

      udpateUndolist(getLabelContent(), editableObject.getPersistentModel(), editableObject, text,
          isAboveLineComment, isBehindLineComment, isLabelComment, getCaretStartPosX(),
          getCaretMousePressedX(), getCaretMousePressedY(), getCaretEndPosX(),
          getCaretMouseReleasedX(), getCaretMouseReleasedY());
    }
  }

  @Override
  protected void updateCaret() {
    if ((m_caretY > -1) && isComment(getCaretEndPosX(), m_caretY)) {
      final ZyLineContent nextLineContent = getLineContent(m_caretY);
      final IZyEditableObject lineObject = nextLineContent.getLineFragmentObjectList().get(0);

      int x = 0;
      if (lineObject != null) {
        x = lineObject.getEnd();
      }

      setCaret(x, x, m_caretY, x, x, m_caretY);
    }
  }

  @Override
  protected void updateClipboard() {
    // do nothing
  }

  @Override
  protected void updateLabelContent() {
    if (m_wasUneditableSelection) {
      return;
    }

    int x = getCaretEndPosX();
    final int y = getCaretMouseReleasedY();

    m_caretY = y + 1;

    if (isComment(x, y)) {
      // Caret end was within a comment. Insert a new line into the comment.

      final ZyLineContent lineContent = getLineContent(y);
      IZyEditableObject lineObject = lineContent.getLineFragmentObjectAt(x);

      if (lineObject.isCommentDelimiter()) {
        x = lineObject.getEnd();

        lineObject = lineContent.getLineFragmentObjectAt(x);
      }

      int textCursor = x - lineObject.getStart();
      final String text =
          lineContent.getText().substring(lineObject.getStart(), lineObject.getEnd());

      if (text.endsWith("\r") && (textCursor == text.length())) {
        --textCursor;
      }

      String changedLine =
          String.format("%s%s%s", text.substring(0, textCursor), "\n", text.substring(textCursor));
      changedLine = getMultilineComment(y, changedLine);

      final IZyEditableObject editableObject = lineContent.getLineObject();

      if (editableObject == null) {
        return;
      }

      if (isAboveLineComment(y)) {
        editableObject.updateComment(changedLine, ECommentPlacement.ABOVE_LINE);
      } else if (isBehindLineComment(x, y)) {
        editableObject.updateComment(changedLine, ECommentPlacement.BEHIND_LINE);
      } else if (isLabelComment(y)) {
        editableObject.update(changedLine);
      }

      getLabelContent().getLineEditor().recreateLabelLines(getLabelContent(),
          editableObject.getPersistentModel());
    } else {
      // Caret was not within a comment. Create a new comment.

      final ZyLineContent lineContent = getLineContent(y);

      final ZyLineContent nextModelLineContent = getNextModelLineContent(y);
      m_caretY = getNextModelLineIndex(y);

      if ((x == lineContent.getText().length()) && (y != 0)) {
        // Caret is at the end of a non-comment line, but it is NOT the first line. A new behind
        // line comment will be created.

        m_caretY = y;

        final IZyEditableObject editableObject = lineContent.getLineObject();

        if (editableObject != null) {
          editableObject.updateComment("\r", ECommentPlacement.BEHIND_LINE);
          getLabelContent().getLineEditor().recreateLabelLines(getLabelContent(),
              editableObject.getPersistentModel());
        }
      } else if ((nextModelLineContent != null) && (nextModelLineContent.getLineObject() != null)
          && !isLabelComment(m_caretY)) {
        // There is a next model line, but it's not the label comment. Add a new comment line to the
        // front.

        String changedComment = "\r";

        if (isComment(0, m_caretY)) {
          // There is already a above line comment
          changedComment = "\n" + getMultiLineComment(m_caretY);
        }

        nextModelLineContent.getLineObject().updateComment(changedComment,
            ECommentPlacement.ABOVE_LINE);
        getLabelContent().getLineEditor().recreateLabelLines(getLabelContent(),
            nextModelLineContent.getLineObject().getPersistentModel());
      } else if ((nextModelLineContent != null) && isLabelComment(m_caretY)) {
        // There is a next model line content and it's the label comment. Add a new comment line to
        // the front.

        String changedComment = "\n";

        changedComment += getMultiLineComment(m_caretY);

        getLabelContent().getModel().update(changedComment);
        getLabelContent().getLineEditor().recreateLabelLines(getLabelContent(),
            getLabelContent().getModel().getPersistentModel());
      } else {
        // There is no next model line content and there is still no label comment appended. Create
        // a new label comment.

        // m_caretY = getLabelContent().getLineCount(); // line count is invalid if other sides
        // label comment has more lines than this one

        m_caretY = y + 1;

        getLabelContent().getModel().update("\r");
        getLabelContent().getLineEditor().recreateLabelLines(getLabelContent(),
            getLabelContent().getModel().getPersistentModel());
      }
    }
  }

  @Override
  protected void updateSelection() {
    m_wasUneditableSelection = !isDeleteableSelection() && isSelection();

    deleteSelection();
  }

  @Override
  protected void updateUndoHistory() {
    final int x = getCaretEndPosX();
    final int y = getCaretMouseReleasedY();

    final ZyLineContent lineContent = getLineContent(y);
    final IZyEditableObject editableObject = lineContent.getLineFragmentObjectAt(x);

    if (editableObject != null) {
      String text =
          lineContent.getText().substring(editableObject.getStart(), editableObject.getEnd());

      if (isComment(x, y)) {
        text = getMultiLineComment(y);
      }

      udpateUndolist(getLabelContent(), lineContent.getLineObject().getPersistentModel(),
          editableObject, text, isAboveLineComment(y), isBehindLineComment(x, y),
          isLabelComment(y), getCaretStartPosX(), getCaretMousePressedX(), getCaretMousePressedY(),
          getCaretEndPosX(), getCaretMouseReleasedX(), getCaretMouseReleasedY());
    }
  }
}
