/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You 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 org.apache.nutch.crawl;

import java.lang.invoke.MethodHandles;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.TreeMap;

import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.MapFile;
import org.apache.hadoop.io.SequenceFile;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.io.MapFile.Writer.Option;
import org.apache.nutch.util.NutchConfiguration;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class TestLinkDbMerger {
  private static final Logger LOG = LoggerFactory
      .getLogger(MethodHandles.lookup().lookupClass());

  String url10 = "http://example.com/foo";
  String[] urls10 = new String[] { "http://example.com/100",
      "http://example.com/101" };

  String url11 = "http://example.com/";
  String[] urls11 = new String[] { "http://example.com/110",
      "http://example.com/111" };

  String url20 = "http://example.com/";
  String[] urls20 = new String[] { "http://foo.com/200", "http://foo.com/201" };
  String url21 = "http://example.com/bar";
  String[] urls21 = new String[] { "http://foo.com/210", "http://foo.com/211" };

  String[] urls10_expected = urls10;
  String[] urls11_expected = new String[] { urls11[0], urls11[1], urls20[0],
      urls20[1] };
  String[] urls20_expected = urls11_expected;
  String[] urls21_expected = urls21;

  TreeMap<String, String[]> init1 = new TreeMap<String, String[]>();
  TreeMap<String, String[]> init2 = new TreeMap<String, String[]>();
  HashMap<String, String[]> expected = new HashMap<String, String[]>();
  Configuration conf;
  Path testDir;
  FileSystem fs;
  LinkDbReader reader;

  @Before
  public void setUp() throws Exception {
    init1.put(url10, urls10);
    init1.put(url11, urls11);
    init2.put(url20, urls20);
    init2.put(url21, urls21);
    expected.put(url10, urls10_expected);
    expected.put(url11, urls11_expected);
    expected.put(url20, urls20_expected);
    expected.put(url21, urls21_expected);
    conf = NutchConfiguration.create();
    fs = FileSystem.get(conf);
    testDir = new Path("build/test/test-linkdb-"
        + new java.util.Random().nextInt());
    fs.mkdirs(testDir);
  }

  @After
  public void tearDown() {
    try {
      if (fs.exists(testDir))
        fs.delete(testDir, true);
    } catch (Exception e) {
    }
    try {
      reader.close();
    } catch (Exception e) {
    }
  }

  @Test
  public void testMerge() throws Exception {
    Configuration conf = NutchConfiguration.create();
    FileSystem fs = FileSystem.get(conf);
    fs.mkdirs(testDir);
    Path linkdb1 = new Path(testDir, "linkdb1");
    Path linkdb2 = new Path(testDir, "linkdb2");
    Path output = new Path(testDir, "output");
    createLinkDb(conf, fs, linkdb1, init1);
    createLinkDb(conf, fs, linkdb2, init2);
    LinkDbMerger merger = new LinkDbMerger(conf);
    LOG.debug("* merging linkdbs to {}", output);
    merger.merge(output, new Path[] { linkdb1, linkdb2 }, false, false);
    LOG.debug("* reading linkdb: {}", output);
    reader = new LinkDbReader(conf, output);
    Iterator<String> it = expected.keySet().iterator();
    while (it.hasNext()) {
      String url = it.next();
      LOG.debug("url={}", url);
      String[] vals = expected.get(url);
      Inlinks inlinks = reader.getInlinks(new Text(url));
      // may not be null
      Assert.assertNotNull(inlinks);
      ArrayList<String> links = new ArrayList<String>();
      Iterator<?> it2 = inlinks.iterator();
      while (it2.hasNext()) {
        Inlink in = (Inlink) it2.next();
        links.add(in.getFromUrl());
      }
      for (int i = 0; i < vals.length; i++) {
        LOG.debug(" -> {}", vals[i]);
        Assert.assertTrue(links.contains(vals[i]));
      }
    }
    reader.close();
    fs.delete(testDir, true);
  }

  private void createLinkDb(Configuration config, FileSystem fs, Path linkdb,
      TreeMap<String, String[]> init) throws Exception {
    LOG.debug("* creating linkdb: {}", linkdb);
    Path dir = new Path(linkdb, LinkDb.CURRENT_NAME);
    
    Option wKeyOpt = MapFile.Writer.keyClass(Text.class);
    org.apache.hadoop.io.SequenceFile.Writer.Option wValueOpt = SequenceFile.Writer.valueClass(Inlinks.class);
    MapFile.Writer writer = new MapFile.Writer(config, new Path(dir,
        "part-00000"), wKeyOpt, wValueOpt);
    Iterator<String> it = init.keySet().iterator();
    while (it.hasNext()) {
      String key = it.next();
      Inlinks inlinks = new Inlinks();
      String[] vals = init.get(key);
      for (int i = 0; i < vals.length; i++) {
        Inlink in = new Inlink(vals[i], vals[i]);
        inlinks.add(in);
      }
      writer.append(new Text(key), inlinks);
    }
    writer.close();
  }
}
