/*
 * 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.ambari.server.api.services.serializers;

import org.apache.ambari.server.api.services.Result;
import org.apache.ambari.server.api.services.ResultImpl;
import org.apache.ambari.server.api.services.ResultStatus;
import org.apache.ambari.server.api.util.TreeNode;
import org.apache.ambari.server.controller.internal.ResourceImpl;
import org.apache.ambari.server.controller.spi.Resource;
import org.apache.commons.csv.CSVFormat;
import org.apache.commons.csv.CSVParser;
import org.apache.commons.csv.CSVRecord;
import org.easymock.EasyMockSupport;
import org.junit.Test;

import java.io.StringReader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;

/**
 * CsvSerializer unit tests
 */
public class CsvSerializerTest extends EasyMockSupport {

  @Test
  public void testSerializeResources_NoColumnInfo() throws Exception {
    Result result = new ResultImpl(true);
    result.setResultStatus(new ResultStatus(ResultStatus.STATUS.OK));
    TreeNode<Resource> tree = result.getResultTree();

    List<TreeMap<String, Object>> data = new ArrayList<TreeMap<String, Object>>() {
      {
        add(new TreeMap<String, Object>() {
          {
            put("property1", "value1a");
            put("property2", "value2a");
            put("property3", "value3a");
            put("property4", "value4a");
          }
        });
        add(new TreeMap<String, Object>() {
          {
            put("property1", "value1'b");
            put("property2", "value2'b");
            put("property3", "value3'b");
            put("property4", "value4'b");
          }
        });
        add(new TreeMap<String, Object>() {
          {
            put("property1", "value1,c");
            put("property2", "value2,c");
            put("property3", "value3,c");
            put("property4", "value4,c");
          }
        });
      }
    };

    tree.setName("items");
    tree.setProperty("isCollection", "true");

    addChildResource(tree, "resource", 0, data.get(0));
    addChildResource(tree, "resource", 1, data.get(1));
    addChildResource(tree, "resource", 2, data.get(2));

    replayAll();

    //execute test
    Object o = new CsvSerializer().serialize(result).toString().replace("\r", "");

    verifyAll();

    assertNotNull(o);

    StringReader reader = new StringReader(o.toString());
    CSVParser csvParser = new CSVParser(reader, CSVFormat.DEFAULT);
    List<CSVRecord> records = csvParser.getRecords();

    assertNotNull(records);
    assertEquals(3, records.size());

    int i = 0;
    for (CSVRecord record : records) {
      TreeMap<String, Object> actualData = data.get(i++);
      assertEquals(actualData.size(), record.size());

      for (String item : record) {
        assertTrue(actualData.containsValue(item));
      }
    }

    csvParser.close();
  }

  @Test
  public void testSerializeResources_HeaderInfo() throws Exception {
    Result result = new ResultImpl(true);
    result.setResultStatus(new ResultStatus(ResultStatus.STATUS.OK));
    TreeNode<Resource> tree = result.getResultTree();
    tree.setName("items");
    tree.setProperty("isCollection", "true");
    tree.setProperty(CsvSerializer.PROPERTY_COLUMN_MAP, new TreeMap<String, String>() {{
      put("propertyD", "Property D");
      put("propertyC", "Property C");
      put("propertyB", "Property B");
      put("propertyA", "Property A");
    }});


    List<Map<String, Object>> data = new ArrayList<Map<String, Object>>() {
      {
        add(new HashMap<String, Object>() {
          {
            put("propertyD", "value1a");
            put("propertyC", "value2a");
            put("propertyB", "value3a");
            put("propertyA", "value4a");
          }
        });
        add(new HashMap<String, Object>() {
          {
            put("propertyD", "value1'b");
            put("propertyC", "value2'b");
            put("propertyB", "value3'b");
            put("propertyA", "value4'b");
          }
        });
        add(new HashMap<String, Object>() {
          {
            put("propertyD", "value1,c");
            put("propertyC", "value2,c");
            put("propertyB", "value3,c");
            put("propertyA", "value4,c");
          }
        });
      }
    };

    addChildResource(tree, "resource", 0, data.get(0));
    addChildResource(tree, "resource", 1, data.get(1));
    addChildResource(tree, "resource", 2, data.get(2));

    replayAll();

    //execute test
    Object o = new CsvSerializer().serialize(result).toString().replace("\r", "");

    verifyAll();


    String expected = "Property A,Property B,Property C,Property D\n" +
        "value4a,value3a,value2a,value1a\n" +
        "value4'b,value3'b,value2'b,value1'b\n" +
        "\"value4,c\",\"value3,c\",\"value2,c\",\"value1,c\"\n";

    assertEquals(expected, o);

  }

  @Test
  public void testSerializeResources_HeaderOrderInfo() throws Exception {
    Result result = new ResultImpl(true);
    result.setResultStatus(new ResultStatus(ResultStatus.STATUS.OK));
    TreeNode<Resource> tree = result.getResultTree();
    tree.setName("items");
    tree.setProperty("isCollection", "true");
    tree.setProperty(CsvSerializer.PROPERTY_COLUMN_MAP, new HashMap<String, String>() {{
      put("property1", "Property 1");
      put("property2", "Property 2");
      put("property3", "Property 3");
      put("property4", "Property 4");
    }});
    tree.setProperty(CsvSerializer.PROPERTY_COLUMN_ORDER, Arrays.asList(
        "property1",
        "property2",
        "property3",
        "property4"));

    addChildResource(tree, "resource", 0, new HashMap<String, Object>() {
      {
        put("property1", "value1a");
        put("property2", "value2a");
        put("property3", "value3a");
        put("property4", "value4a");
      }
    });
    addChildResource(tree, "resource", 1, new HashMap<String, Object>() {
      {
        put("property1", "value1'b");
        put("property2", "value2'b");
        put("property3", "value3'b");
        put("property4", "value4'b");
      }
    });
    addChildResource(tree, "resource", 2, new HashMap<String, Object>() {
      {
        put("property1", "value1,c");
        put("property2", "value2,c");
        put("property3", "value3,c");
        put("property4", "value4,c");
      }
    });

    replayAll();

    //execute test
    Object o = new CsvSerializer().serialize(result).toString().replace("\r", "");

    String expected = "Property 1,Property 2,Property 3,Property 4\n" +
        "value1a,value2a,value3a,value4a\n" +
        "value1'b,value2'b,value3'b,value4'b\n" +
        "\"value1,c\",\"value2,c\",\"value3,c\",\"value4,c\"\n";

    assertEquals(expected, o);

    verifyAll();
  }


  private void addChildResource(TreeNode<Resource> parent, String name, int index, final Map<String, Object> data) {
    Resource resource = new ResourceImpl(Resource.Type.Cluster);

    if (data != null) {
      for (Map.Entry<String, Object> entry : data.entrySet()) {
        resource.setProperty(entry.getKey(), entry.getValue());
      }
    }

    parent.addChild(resource, String.format("%s:%d", name, index));
  }

}
