/*
 * Copyright 2000-2016 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 com.intellij.openapi.diff.impl.incrementalMerge;

import com.intellij.openapi.actionSystem.AnAction;
import com.intellij.openapi.diff.impl.highlighting.FragmentSide;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.editor.ex.DocumentEx;
import com.intellij.openapi.util.TextRange;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

public abstract class TwoSideChange<T extends TwoSideChange.SideChange> extends ChangeSide implements DiffRangeMarker.RangeInvalidListener {
  @NotNull protected final MergeList myMergeList;
  @NotNull protected DiffRangeMarker myBaseRangeMarker;
  protected T myLeftChange;
  protected T myRightChange;
  @NotNull protected final ChangeHighlighterHolder myCommonHighlighterHolder;

  protected TwoSideChange(@NotNull TextRange baseRange,
                          @NotNull MergeList mergeList,
                          @NotNull ChangeHighlighterHolder highlighterHolder) {
    myBaseRangeMarker = new DiffRangeMarker((DocumentEx)mergeList.getBaseDocument(), baseRange, this);
    myMergeList = mergeList;
    myCommonHighlighterHolder = highlighterHolder;
  }

  @NotNull
  public ChangeHighlighterHolder getHighlighterHolder() {
    return myCommonHighlighterHolder;
  }

  @NotNull
  public DiffRangeMarker getRange() {
    return myBaseRangeMarker;
  }

  @Nullable
  public Change getLeftChange() {
    return myLeftChange;
  }

  @Nullable
  public Change getRightChange() {
    return myRightChange;
  }

  public void setRange(@NotNull DiffRangeMarker range) {
    myBaseRangeMarker = range;
  }

  @Nullable
  T getOtherChange(@NotNull T change) {
    if (change == myLeftChange) {
      return myRightChange;
    }
    else if (change == myRightChange) {
      return myLeftChange;
    }
    else {
      throw new IllegalStateException("Unexpected change: " + change);
    }
  }

  public void removeOtherChange(@NotNull T change) {
    if (change == myLeftChange) {
      myRightChange = null;
    }
    else if (change == myRightChange) {
      myLeftChange = null;
    }
    else {
      throw new IllegalStateException("Unexpected change: " + change);
    }
  }

  public void conflictRemoved() {
    removeHighlighters(myLeftChange);
    removeHighlighters(myRightChange);
    myCommonHighlighterHolder.removeHighlighters();
    myMergeList.removeChanges(myLeftChange, myRightChange);
    myBaseRangeMarker.removeListener(this);
  }

  private static <T extends SideChange> void removeHighlighters(@Nullable T change) {
    if (change != null) {
      change.getOriginalSide().getHighlighterHolder().removeHighlighters();
    }
  }

  @NotNull
  public Document getOriginalDocument(FragmentSide mergeSide) {
    return myMergeList.getChanges(mergeSide).getDocument(MergeList.BRANCH_SIDE);
  }

  public void onRangeInvalidated() {
    conflictRemoved();
  }

  @NotNull
  public MergeList getMergeList() {
    return myMergeList;
  }

  protected static abstract class SideChange<V extends TwoSideChange> extends Change implements DiffRangeMarker.RangeInvalidListener {
    protected V myTwoSideChange;
    @NotNull protected final ChangeList myChangeList;

    protected SimpleChangeSide myOriginalSide;
    @NotNull protected ChangeType myType;

    protected SideChange(@NotNull V twoSideChange,
                         @NotNull ChangeList changeList,
                         @NotNull ChangeType type,
                         @NotNull FragmentSide mergeSide,
                         @NotNull TextRange versionRange) {
      myTwoSideChange = twoSideChange;
      myChangeList = changeList;
      myOriginalSide =
        new SimpleChangeSide(mergeSide, new DiffRangeMarker((DocumentEx)twoSideChange.getOriginalDocument(mergeSide), versionRange, this));
      myType = type;
    }

    @NotNull
    public ChangeType getType() {
      return myType;
    }

    public SimpleChangeSide getOriginalSide() {
      return myOriginalSide;
    }

    protected void markApplied() {
      myType = ChangeType.deriveApplied(myType);
      myChangeList.apply(this);

      myOriginalSide.getHighlighterHolder().updateHighlighter(myOriginalSide, myType);
      myOriginalSide.getHighlighterHolder().setActions(AnAction.EMPTY_ARRAY);

      // display, what one side of the conflict was resolved to
      myTwoSideChange.getHighlighterHolder().updateHighlighter(myTwoSideChange, myType);
    }

    public ChangeList getChangeList() {
      return myTwoSideChange.getMergeList().getChanges(myOriginalSide.getFragmentSide());
    }

    @Override
    protected void changeSide(ChangeSide sideToChange, DiffRangeMarker newRange) {
      myTwoSideChange.setRange(newRange);
    }

    @NotNull
    @Override
    public ChangeSide getChangeSide(@NotNull FragmentSide side) {
      return isBranch(side) ? myOriginalSide : myTwoSideChange;
    }

    protected static boolean isBranch(@NotNull FragmentSide side) {
      return MergeList.BRANCH_SIDE == side;
    }

    protected void removeFromList() {
      myTwoSideChange.conflictRemoved();
      myTwoSideChange = null;
    }

    public boolean isValid() {
      return myTwoSideChange != null;
    }

    public void onRemovedFromList() {
      myOriginalSide.getRange().removeListener(this);
      myTwoSideChange = null;
      myOriginalSide = null;
    }

    public void onRangeInvalidated() {
      removeFromList();
    }
  }
}
