package eu.chargetime.ocpp.test;

import static org.hamcrest.CoreMatchers.*;
import static org.hamcrest.MatcherAssert.assertThat;

import eu.chargetime.ocpp.SOAPCommunicator;
import eu.chargetime.ocpp.Transmitter;
import eu.chargetime.ocpp.model.SOAPHostInfo;
import eu.chargetime.ocpp.model.TestModel;
import eu.chargetime.ocpp.model.core.BootNotificationConfirmation;
import eu.chargetime.ocpp.model.core.BootNotificationRequest;
import eu.chargetime.ocpp.model.core.RegistrationStatus;
import java.io.StringReader;
import java.io.StringWriter;
import java.time.ZonedDateTime;
import java.util.Locale;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;
import org.w3c.dom.Document;
import org.xml.sax.InputSource;

/*
   ChargeTime.eu - Java-OCA-OCPP

   MIT License

   Copyright (C) 2016-2018 Thomas Volden <[email protected]>
   Copyright (C) 2019 Kevin Raddatz <[email protected]>

   Permission is hereby granted, free of charge, to any person obtaining a copy
   of this software and associated documentation files (the "Software"), to deal
   in the Software without restriction, including without limitation the rights
   to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
   copies of the Software, and to permit persons to whom the Software is
   furnished to do so, subject to the following conditions:

   The above copyright notice and this permission notice shall be included in all
   copies or substantial portions of the Software.

   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
   IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
   FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
   AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
   LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
   OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
   SOFTWARE.
*/
@RunWith(MockitoJUnitRunner.class)
public class SOAPCommunicatorTest {

  private SOAPCommunicator communicator;
  private String chargeBoxIdentity = "testIdentity";
  private String fromUrl = "http://localhost";
  private String namespace = "urn://Ocpp/Cs/2015/10";

  @Mock private Transmitter transmitter;

  public static String docToString(Document doc) {
    try {
      StringWriter sw = new StringWriter();
      TransformerFactory tf = TransformerFactory.newInstance();
      Transformer transformer = tf.newTransformer();
      transformer.transform(new DOMSource(doc), new StreamResult(sw));
      return sw.toString();
    } catch (Exception ex) {
      throw new RuntimeException("Error converting to String", ex);
    }
  }

  public static Document stringToDocument(String xml) throws Exception {
    DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
    factory.setNamespaceAware(true);
    DocumentBuilder db = factory.newDocumentBuilder();

    InputSource is = new InputSource();
    is.setCharacterStream(new StringReader(xml));
    return db.parse(is);
  }

  @Before
  public void setup() {
    SOAPHostInfo hostInfo =
        new SOAPHostInfo.Builder()
            .chargeBoxIdentity(chargeBoxIdentity)
            .fromUrl(fromUrl)
            .namespace(namespace)
            .build();
    communicator = new SOAPCommunicator(hostInfo, transmitter);
  }

  @Test
  @Ignore
  public void unpackPayload_emptyPayload_returnRequestedType() throws Exception {
    // Given
    Document payload =
        stringToDocument(
            "<bootNotificationRequest xmlns=\"urn://Ocpp/Cs/2015/10\"></bootNotificationRequest>");
    Class<?> type = BootNotificationRequest.class;

    // When
    Object result = communicator.unpackPayload(payload, type);

    // Then
    assertThat(result, instanceOf(type));
  }

  @Test
  @Ignore("Only works for ocpp namespaces")
  public void unpackPayload_aStringPayload_returnsTestModelWithAString() throws Exception {
    // Given
    String aString = "Some string";
    String xml = "<testModel><stringTest>%s</stringTest></testModel>";
    Document payload = stringToDocument(String.format(xml, aString));

    // When
    TestModel model = communicator.unpackPayload(payload, TestModel.class);

    // Then
    assertThat(model.getStringTest(), equalTo(aString));
  }

  @Test
  @Ignore("Only works for ocpp namespaces")
  public void unpackPayload_aCalendarPayload_returnsTestModelWithACalendar() throws Exception {
    // Given
    String aCalendar = "2016-04-28T07:16:11.988Z";
    String xml = "<testModel><calendarTest>%s</calendarTest></testModel>";
    Document payload = stringToDocument(String.format(xml, aCalendar));

    ZonedDateTime someDate = ZonedDateTime.parse("2016.04.28T07:16:11.988Z");

    // When
    TestModel model = communicator.unpackPayload(payload, TestModel.class);

    // Then
    assertThat(model.getCalendarTest().compareTo(someDate), is(0));
  }

  @Test
  @Ignore("Only works for ocpp namespaces")
  public void unpackPayload_anIntegerPayload_returnsTestModelWithAnInteger() throws Exception {
    // Given
    Integer anInteger = 1337;
    String xml = "<testModel><integerTest>%d</integerTest></testModel>";
    Document payload = stringToDocument(String.format(xml, anInteger));

    // When
    TestModel model = communicator.unpackPayload(payload, TestModel.class);

    // Then
    assertThat(model.getIntegerTest(), equalTo(anInteger));
  }

  @Test
  @Ignore("Only works for ocpp namespaces")
  public void unpackPayload_aGenericIntPayload_returnsTestModelWithAGenericInt() throws Exception {
    // Given
    int anInteger = 1337;
    String xml = "<testModel><intTest>%d</intTest></testModel>";
    Document payload = stringToDocument(String.format(xml, anInteger));

    // When
    TestModel model = communicator.unpackPayload(payload, TestModel.class);

    // Then
    assertThat(model.getIntTest(), equalTo(anInteger));
  }

  @Test
  @Ignore("Only works for ocpp namespaces")
  public void unpackPayload_aLongPayload_returnsTestModelWithALong() throws Exception {
    // Given
    Long aLong = 1337L;
    String xml = "<testModel><longTest>%d</longTest></testModel>";
    Document payload = stringToDocument(String.format(xml, aLong));

    // When
    TestModel model = communicator.unpackPayload(payload, TestModel.class);

    // Then
    assertThat(model.getLongTest(), equalTo(aLong));
  }

  @Test
  @Ignore("Only works for ocpp namespaces")
  public void unpackPayload_aGenericLongPayload_returnsTestModelWithAGenericLong()
      throws Exception {
    // Given
    long aLong = 1337;
    String xml = "<testModel><genericLongTest>%d</genericLongTest></testModel>";
    Document payload = stringToDocument(String.format(xml, aLong));

    // When
    TestModel model = communicator.unpackPayload(payload, TestModel.class);

    // Then
    assertThat(model.getGenericLongTest(), equalTo(aLong));
  }

  @Test
  @Ignore("Only works for ocpp namespaces")
  public void unpackPayload_aDoublePayload_returnsTestModelWithADouble() throws Exception {
    // Given
    Double aDouble = 13.37D;
    String xml = "<testModel><doubleTest>%f</doubleTest></testModel>";
    Document payload = stringToDocument(String.format(Locale.US, xml, aDouble));

    // When
    TestModel model = communicator.unpackPayload(payload, TestModel.class);

    // Then
    assertThat(model.getDoubleTest(), equalTo(aDouble));
  }

  @Test
  @Ignore("Only works for ocpp namespaces")
  public void unpackPayload_aGenericDoublePayload_returnsTestModelWithAGenericDouble()
      throws Exception {
    // Given
    double aDouble = 13.37;
    String xml = "<testModel><genericDoubleTest>%f</genericDoubleTest></testModel>";
    Document payload = stringToDocument(String.format(Locale.US, xml, aDouble));

    // When
    TestModel model = communicator.unpackPayload(payload, TestModel.class);

    // Then
    assertThat(model.getGenericDoubleTest(), equalTo(aDouble));
  }

  @Test
  @Ignore("Only works for ocpp namespaces")
  public void unpackPayload_aBooleanPayload_returnsTestModelWithABoolean() throws Exception {
    // Given
    Boolean aBoolean = false;
    String xml = "<testModel><booleanTest>%b</booleanTest></testModel>";
    Document payload = stringToDocument(String.format(xml, aBoolean));

    // When
    TestModel model = communicator.unpackPayload(payload, TestModel.class);

    // Then
    assertThat(model.getBooleanTest(), equalTo(aBoolean));
  }

  @Test
  @Ignore
  public void unpackPayload_aGenericBooleanPayload_returnsTestModelWithAGenericBoolean()
      throws Exception {
    // Given
    boolean aBoolean = false;
    String xml = "<testModel><genericBooleanTest>%b</genericBooleanTest></testModel>";
    Document payload = stringToDocument(String.format(xml, aBoolean));

    // When
    TestModel model = communicator.unpackPayload(payload, TestModel.class);

    // Then
    assertThat(model.isGenericBoleanTest(), equalTo(aBoolean));
  }

  @Test
  @Ignore("Only works for ocpp namespaces")
  public void unpackPayload_anObjectPayload_returnsTestModelWithAnObject() throws Exception {
    // Given
    String xml = "<testModel><objectTest></objectTest></testModel>";
    Document payload = stringToDocument(xml);

    // When
    TestModel model = communicator.unpackPayload(payload, TestModel.class);

    // Then
    assertThat(model.getObjectTest(), instanceOf(TestModel.class));
  }

  @Test
  @Ignore("Only works for ocpp namespaces")
  public void unpackPayload_anArrayOfInts_returnsTestModelWithAnArrayOfInts() throws Exception {
    // Given
    Integer[] anArray = {1, 2, 3};
    String xml =
        "<testModel><arrayTest>1</arrayTest><arrayTest>2</arrayTest><arrayTest>3</arrayTest></testModel>";
    Document payload = stringToDocument(xml);

    // When
    TestModel model = communicator.unpackPayload(payload, TestModel.class);

    // Then
    assertThat(model.getArrayTest(), equalTo(anArray));
  }

  @Test
  @Ignore
  public void unpackPayload_bootNotificationCallResultPayload_returnBootNotificationConfirmation()
      throws Exception {
    // Given
    String currentType = "2016-04-28T07:16:11.988Z";
    ZonedDateTime someDate = ZonedDateTime.parse("2016-04-28T07:16:11.988Z");
    int interval = 300;
    RegistrationStatus status = RegistrationStatus.Accepted;
    String xml =
        "<bootNotificationResponse xmlns=\"urn://Ocpp/Cs/2015/10/\"><currentTime>%s</currentTime><interval>%d</interval><status>%s</status></bootNotificationResponse>";
    Document payload = stringToDocument(String.format(xml, currentType, interval, status));
    Class<?> type = BootNotificationConfirmation.class;

    // When
    Object result = communicator.unpackPayload(payload, type);

    // Then
    assertThat(result, instanceOf(type));
    assertThat(((BootNotificationConfirmation) result).getCurrentTime().compareTo(someDate), is(0));
    assertThat(((BootNotificationConfirmation) result).getStatus(), is(status));
    assertThat(((BootNotificationConfirmation) result).getInterval(), is(interval));
  }

  @Test
  @Ignore
  public void pack_bootNotificationRequest_returnsBootNotificationRequestPayload() {
    // Given
    String expected =
        "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?><bootNotificationRequest xmlns=\"urn://Ocpp/Cs/2015/10\"><chargePointVendor>VendorX</chargePointVendor><chargePointModel>SingleSocketCharger</chargePointModel></bootNotificationRequest>";

    BootNotificationRequest request = new BootNotificationRequest("VendorX", "SingleSocketCharger");

    // When
    Document payload = (Document) communicator.packPayload(request);

    // Then
    assertThat(docToString(payload), equalTo(expected));
  }
}