/**
 * 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.hadoop.streaming.mapreduce;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;

import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.FSDataInputStream;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Job;
import org.apache.hadoop.mapreduce.Mapper;
import org.apache.hadoop.mapreduce.lib.input.FileInputFormat;
import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

/**
 * This class tests StreamXmlRecordReader The test creates an XML file, uses
 * StreamXmlRecordReader and compares the expected output against the generated
 * output
 */
public class TestStreamXmlRecordReader {

  private File INPUT_FILE;
  private String input;
  private String outputExpect;
  Path OUTPUT_DIR;
  FileSystem fs;

  public TestStreamXmlRecordReader() throws IOException {
    INPUT_FILE = new File("target/input.xml");
    input = "<xmltag>\t\nroses.are.red\t\nviolets.are.blue\t\n"
        + "bunnies.are.pink\t\n</xmltag>\t\n";
    outputExpect = input;
  }

  protected void assertOutput(String expectedOutput, String output)
      throws IOException {
    String[] words = expectedOutput.split("\t\n");
    Set<String> expectedWords = new HashSet<String>(Arrays.asList(words));
    words = output.split("\t\n");
    Set<String> returnedWords = new HashSet<String>(Arrays.asList(words));
    assertTrue(returnedWords.containsAll(expectedWords));
  }

  protected void checkOutput() throws IOException {
    File outFile = new File(OUTPUT_DIR.toString());
    Path outPath = new Path(outFile.getAbsolutePath(), "part-r-00000");
    String output = slurpHadoop(outPath, fs);
    fs.delete(outPath, true);
    outputExpect = "<PATTERN>\n" + outputExpect + "</PATTERN>";
    System.err.println("outEx1=" + outputExpect);
    System.err.println("  out1=" + output);
    assertOutput(outputExpect, output);
  }

  private String slurpHadoop(Path p, FileSystem fs) throws IOException {
    int len = (int) fs.getFileStatus(p).getLen();
    byte[] buf = new byte[len];
    FSDataInputStream in = fs.open(p);
    String contents = null;
    try {
      in.readFully(in.getPos(), buf);
      contents = new String(buf, StandardCharsets.UTF_8);
    } finally {
      in.close();
    }
    return contents;
  }

  @BeforeEach
  public void createInput() throws IOException {
    FileOutputStream out = new FileOutputStream(INPUT_FILE.getAbsoluteFile());
    String dummyXmlStartTag = "<PATTERN>\n";
    String dummyXmlEndTag = "</PATTERN>\n";
    out.write(dummyXmlStartTag.getBytes(StandardCharsets.UTF_8));
    out.write(input.getBytes(StandardCharsets.UTF_8));
    out.write(dummyXmlEndTag.getBytes(StandardCharsets.UTF_8));
    out.close();
  }

  @Test
  public void testStreamXmlRecordReader() throws Exception {

    Job job = Job.getInstance();
    Configuration conf = job.getConfiguration();
    job.setJarByClass(TestStreamXmlRecordReader.class);
    job.setMapperClass(Mapper.class);
    conf.set("stream.recordreader.class",
        "org.apache.hadoop.streaming.mapreduce.StreamXmlRecordReader");
    conf.set("stream.recordreader.begin", "<PATTERN>");
    conf.set("stream.recordreader.end", "</PATTERN>");
    job.setInputFormatClass(StreamInputFormat.class);
    job.setMapOutputKeyClass(Text.class);
    job.setMapOutputValueClass(Text.class);
    job.setOutputKeyClass(Text.class);
    job.setOutputValueClass(Text.class);
    FileInputFormat.addInputPath(job, new Path("target/input.xml"));
    OUTPUT_DIR = new Path("target/output");
    fs = FileSystem.get(conf);
    if (fs.exists(OUTPUT_DIR)) {
      fs.delete(OUTPUT_DIR, true);
    }
    FileOutputFormat.setOutputPath(job, OUTPUT_DIR);
    boolean ret = job.waitForCompletion(true);

    assertEquals(true, ret);
    checkOutput();

  }

  @AfterEach
  public void tearDown() throws IOException {
    fs.delete(OUTPUT_DIR, true);
  }

}
