// Copyright (C) 2013 The Android Open Source Project
//
// 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.gerrit.server.change;

import static org.easymock.EasyMock.createMock;
import static org.easymock.EasyMock.expect;
import static org.easymock.EasyMock.expectLastCall;
import static org.easymock.EasyMock.replay;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.fail;

import com.google.common.base.Objects;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Lists;
import com.google.gerrit.common.changes.Side;
import com.google.gerrit.extensions.registration.DynamicMap;
import com.google.gerrit.extensions.restapi.IdString;
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
import com.google.gerrit.extensions.restapi.RestReadView;
import com.google.gerrit.extensions.restapi.RestView;
import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.reviewdb.client.CommentRange;
import com.google.gerrit.reviewdb.client.Patch;
import com.google.gerrit.reviewdb.client.PatchLineComment;
import com.google.gerrit.reviewdb.client.PatchLineComment.Status;
import com.google.gerrit.reviewdb.client.PatchSet;
import com.google.gerrit.reviewdb.server.PatchLineCommentAccess;
import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.account.AccountInfo;
import com.google.gerrit.server.util.TimeUtil;
import com.google.gwtorm.server.ListResultSet;
import com.google.gwtorm.server.ResultSet;
import com.google.inject.AbstractModule;
import com.google.inject.Guice;
import com.google.inject.Injector;
import com.google.inject.TypeLiteral;

import org.easymock.IAnswer;
import org.junit.Before;
import org.junit.Test;

import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;

public class CommentsTest {

  private Injector injector;
  private RevisionResource revRes1;
  private RevisionResource revRes2;
  private PatchLineComment plc1;
  private PatchLineComment plc2;
  private PatchLineComment plc3;

  @Before
  public void setUp() throws Exception {
    @SuppressWarnings("unchecked")
    final DynamicMap<RestView<CommentResource>> views =
        createMock(DynamicMap.class);
    final TypeLiteral<DynamicMap<RestView<CommentResource>>> viewsType =
        new TypeLiteral<DynamicMap<RestView<CommentResource>>>() {};
    final AccountInfo.Loader.Factory alf =
        createMock(AccountInfo.Loader.Factory.class);
    final ReviewDb db = createMock(ReviewDb.class);

    AbstractModule mod = new AbstractModule() {
      @Override
      protected void configure() {
        bind(viewsType).toInstance(views);
        bind(AccountInfo.Loader.Factory.class).toInstance(alf);
        bind(ReviewDb.class).toInstance(db);
      }};

    Account.Id account1 = new Account.Id(1);
    Account.Id account2 = new Account.Id(2);
    AccountInfo.Loader accountLoader = createMock(AccountInfo.Loader.class);
    accountLoader.fill();
    expectLastCall().anyTimes();
    expect(accountLoader.get(account1))
        .andReturn(new AccountInfo(account1)).anyTimes();
    expect(accountLoader.get(account2))
        .andReturn(new AccountInfo(account2)).anyTimes();
    expect(alf.create(true)).andReturn(accountLoader).anyTimes();
    replay(accountLoader, alf);

    revRes1 = createMock(RevisionResource.class);
    revRes2 = createMock(RevisionResource.class);

    PatchLineCommentAccess plca = createMock(PatchLineCommentAccess.class);
    expect(db.patchComments()).andReturn(plca).anyTimes();

    Change.Id changeId = new Change.Id(123);
    PatchSet.Id psId1 = new PatchSet.Id(changeId, 1);
    PatchSet ps1 = new PatchSet(psId1);
    expect(revRes1.getPatchSet()).andReturn(ps1).anyTimes();
    PatchSet.Id psId2 = new PatchSet.Id(changeId, 2);
    PatchSet ps2 = new PatchSet(psId2);
    expect(revRes2.getPatchSet()).andReturn(ps2);

    long timeBase = TimeUtil.nowMs();
    plc1 = newPatchLineComment(psId1, "Comment1", null,
        "FileOne.txt", Side.REVISION, 1, account1, timeBase,
        "First Comment", new CommentRange(1, 2, 3, 4));
    plc2 = newPatchLineComment(psId1, "Comment2", "Comment1",
        "FileOne.txt", Side.REVISION, 1, account2, timeBase + 1000,
        "Reply to First Comment",  new CommentRange(1, 2, 3, 4));
    plc3 = newPatchLineComment(psId1, "Comment3", "Comment1",
        "FileOne.txt", Side.PARENT, 1, account1, timeBase + 2000,
        "First Parent Comment",  new CommentRange(1, 2, 3, 4));

    expect(plca.publishedByPatchSet(psId1))
        .andAnswer(results(plc1, plc2, plc3)).anyTimes();
    expect(plca.publishedByPatchSet(psId2))
        .andAnswer(results()).anyTimes();

    replay(db, revRes1, revRes2, plca);
    injector = Guice.createInjector(mod);
  }

  @Test
  public void testListComments() throws Exception {
    // test ListComments for patch set 1
    assertListComments(injector, revRes1, ImmutableMap.of(
        "FileOne.txt", Lists.newArrayList(plc3, plc1, plc2)));

    // test ListComments for patch set 2
    assertListComments(injector, revRes2,
        Collections.<String, ArrayList<PatchLineComment>>emptyMap());
  }

  @Test
  public void testGetComment() throws Exception {
    // test GetComment for existing comment
    assertGetComment(injector, revRes1, plc1, plc1.getKey().get());

    // test GetComment for non-existent comment
    assertGetComment(injector, revRes1, null, "BadComment");
  }

  private static IAnswer<ResultSet<PatchLineComment>> results(
      final PatchLineComment... comments) {
    return new IAnswer<ResultSet<PatchLineComment>>() {
      @Override
      public ResultSet<PatchLineComment> answer() throws Throwable {
        return new ListResultSet<PatchLineComment>(Lists.newArrayList(comments));
      }};
  }

  private static void assertGetComment(Injector inj, RevisionResource res,
      PatchLineComment expected, String uuid) throws Exception {
    GetComment getComment = inj.getInstance(GetComment.class);
    Comments comments = inj.getInstance(Comments.class);
    try {
      CommentResource commentRes = comments.parse(res, IdString.fromUrl(uuid));
      if (expected == null) {
        fail("Expected no comment");
      }
      CommentInfo actual = getComment.apply(commentRes);
      assertComment(expected, actual);
    } catch (ResourceNotFoundException e) {
      if (expected != null) {
        fail("Expected to find comment");
      }
    }
  }

  private static void assertListComments(Injector inj, RevisionResource res,
      Map<String, ArrayList<PatchLineComment>> expected) throws Exception {
    Comments comments = inj.getInstance(Comments.class);
    RestReadView<RevisionResource> listView =
        (RestReadView<RevisionResource>) comments.list();
    @SuppressWarnings("unchecked")
    Map<String, List<CommentInfo>> actual =
        (Map<String, List<CommentInfo>>) listView.apply(res);
    assertNotNull(actual);
    assertEquals(expected.size(), actual.size());
    assertEquals(expected.keySet(), actual.keySet());
    for (Map.Entry<String, ArrayList<PatchLineComment>> entry : expected.entrySet()) {
      List<PatchLineComment> expectedComments = entry.getValue();
      List<CommentInfo> actualComments = actual.get(entry.getKey());
      assertNotNull(actualComments);
      assertEquals(expectedComments.size(), actualComments.size());
      for (int i = 0; i < expectedComments.size(); i++) {
        assertComment(expectedComments.get(i), actualComments.get(i));
      }
    }
  }

  private static void assertComment(PatchLineComment plc, CommentInfo ci) {
    assertEquals(plc.getKey().get(), ci.id);
    assertEquals(plc.getParentUuid(), ci.inReplyTo);
    assertEquals("gerritcodereview#comment", ci.kind);
    assertEquals(plc.getMessage(), ci.message);
    assertNotNull(ci.author);
    assertEquals(plc.getAuthor(), ci.author._id);
    assertEquals(plc.getLine(), (int) ci.line);
    assertEquals(plc.getSide() == 0 ? Side.PARENT : Side.REVISION,
        Objects.firstNonNull(ci.side, Side.REVISION));
    assertEquals(plc.getWrittenOn(), ci.updated);
    assertEquals(plc.getRange(), ci.range);
  }

  private static PatchLineComment newPatchLineComment(PatchSet.Id psId,
      String uuid, String inReplyToUuid, String filename, Side side, int line,
      Account.Id authorId, long millis, String message, CommentRange range) {
    Patch.Key p = new Patch.Key(psId, filename);
    PatchLineComment.Key id = new PatchLineComment.Key(p, uuid);
    PatchLineComment plc =
        new PatchLineComment(id, line, authorId, inReplyToUuid, TimeUtil.nowTs());
    plc.setMessage(message);
    plc.setRange(range);
    plc.setSide(side == Side.PARENT ? (short) 0 : (short) 1);
    plc.setStatus(Status.PUBLISHED);
    plc.setWrittenOn(new Timestamp(millis));
    return plc;
  }
}
