/*******************************************************************************
 * 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.olingo.odata2.fit.ref;

import static org.custommonkey.xmlunit.XMLAssert.assertXpathEvaluatesTo;
import static org.custommonkey.xmlunit.XMLAssert.assertXpathNotExists;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;

import java.lang.reflect.Field;

import org.apache.http.HttpHeaders;
import org.apache.http.HttpResponse;
import org.apache.olingo.odata2.api.commons.HttpContentType;
import org.apache.olingo.odata2.api.commons.HttpStatusCodes;
import org.apache.olingo.odata2.ref.model.DataContainer;
import org.apache.olingo.odata2.ref.model.Photo;
import org.apache.olingo.odata2.ref.processor.ListsProcessor;
import org.apache.olingo.odata2.ref.processor.ScenarioDataSource;
import org.apache.olingo.odata2.testutil.server.ServletType;
import org.custommonkey.xmlunit.XMLAssert;
import org.junit.Test;

/**
 * Tests employing the reference scenario reading a single entity in XML format.
 * 
 */
public class EntryXmlReadOnlyTest extends AbstractRefXmlTest {

  public EntryXmlReadOnlyTest(final ServletType servletType) {
    super(servletType);
  }

  @Test
  public void entry() throws Exception {
    HttpResponse response = callUri("Employees('2')");
    checkMediaType(response, HttpContentType.APPLICATION_ATOM_XML_UTF8 + ";type=entry");
    assertNull(response.getFirstHeader(HttpHeaders.ETAG));
    assertXpathEvaluatesTo(EMPLOYEE_2_NAME, "/atom:entry/m:properties/d:EmployeeName", getBody(response));

    response = callUri("Managers('3')");
    checkMediaType(response, HttpContentType.APPLICATION_ATOM_XML_UTF8 + ";type=entry");
    assertXpathEvaluatesTo(EMPLOYEE_3_NAME, "/atom:entry/m:properties/d:EmployeeName", getBody(response));

    response = callUri("Teams('2')");
    checkMediaType(response, HttpContentType.APPLICATION_ATOM_XML_UTF8 + ";type=entry");
    assertXpathEvaluatesTo("2", "/atom:entry/atom:content/m:properties/d:Id", getBody(response));

    response = callUri("Rooms('1')");
    checkMediaType(response, HttpContentType.APPLICATION_ATOM_XML_UTF8 + ";type=entry");
    checkEtag(response, "W/\"1\"");
    String body = getBody(response);
    assertXpathEvaluatesTo("W/\"1\"", "/atom:entry/@m:etag", body);
    assertXpathEvaluatesTo("1", "/atom:entry/atom:content/m:properties/d:Id", body);

    response = callUri("Rooms('1')?$expand=nr_Employees");
    checkMediaType(response, HttpContentType.APPLICATION_ATOM_XML_UTF8 + ";type=entry");
    // assertNull(response.getFirstHeader(HttpHeaders.ETAG));
    assertXpathEvaluatesTo(
        EMPLOYEE_1_NAME,
        "/atom:entry/atom:link[@href=\"Rooms('1')/nr_Employees\"]" +
            "/m:inline/atom:feed/atom:entry/m:properties/d:EmployeeName",
        getBody(response));

    response = callUri("Container2.Photos(Id=1,Type='image%2Fpng')");
    checkMediaType(response, HttpContentType.APPLICATION_ATOM_XML_UTF8 + ";type=entry");
    body = getBody(response);
    assertXpathEvaluatesTo("image/png", "/atom:entry/m:properties/d:Type", body);
    assertXpathEvaluatesTo(IMAGE_JPEG, "/atom:entry/m:properties/d:Image/@m:MimeType", body);
    assertXpathEvaluatesTo(PHOTO_DEFAULT_IMAGE, "/atom:entry/m:properties/d:Image", body);

    response = callUri("Container2.Photos(Id=4,Type='foo')");
    checkMediaType(response, HttpContentType.APPLICATION_ATOM_XML_UTF8 + ";type=entry");
    assertXpathEvaluatesTo("Container2.Photos(Id=4,Type='foo')/$value", "/atom:entry/atom:content/@src",
        getBody(response));

    notFound("Managers('5')");
    notFound("Managers('1%2C2')");
    badRequest("Managers('3')?$top=1");
    badRequest("Rooms(X'33')");
  }

  @Test
  public void entryWithSpecialKey() throws Exception {
    // Ugly hack to create an entity with a key containing special characters just for this test.
    ListsProcessor processor = (ListsProcessor) getService().getEntityProcessor();
    Field field = getField(processor.getClass(), "dataSource");
    field.setAccessible(true);
    ScenarioDataSource dataSource = (ScenarioDataSource) field.get(processor);
    field = getField(dataSource.getClass(), "dataContainer");
    field.setAccessible(true);
    DataContainer dataContainer = (DataContainer) field.get(dataSource);
    // Add a new Photo where the "Type" property is set to, space-separated,
    // a literal percent character, all gen-delims, and all sub-delims
    // as specified by RFC 3986.
    dataContainer.getPhotos().add(new Photo(42, "strange Photo", "% :/?#[]@ !$&'()*+,;="));

    // Check that percent-decoding and -encoding works as expected.
    final String url =
        "Container2.Photos(Id=42,Type='%25%20%3A%2F%3F%23%5B%5D%40%20%21%24%26%27%28%29%2A%2B%2C%3B%3D')";
    final String expected = url.replace("%27", "''");
    final HttpResponse response = callUri(url);
    final String body = getBody(response);
    assertXpathEvaluatesTo("strange Photo", "/atom:entry/m:properties/d:Name", body);
    assertXpathEvaluatesTo(expected, "/atom:entry/atom:link[@rel=\"edit\"]/@href", body);
    assertXpathEvaluatesTo(expected + "/$value", "/atom:entry/atom:link[@rel=\"edit-media\"]/@href", body);
  }

  private Field getField(final Class<?> clazz, final String string) throws Exception {
    try {
      return clazz.getDeclaredField(string);
    } catch (NoSuchFieldException e) {
      if (clazz == Object.class) {
        throw e;
      }
      return getField(clazz.getSuperclass(), string);
    }
  }

  @Test
  public void expand() throws Exception {
    HttpResponse response = callUri("Employees('5')?$expand=ne_Manager");
    checkMediaType(response, HttpContentType.APPLICATION_ATOM_XML_UTF8 + ";type=entry");
    String body = getBody(response);
    assertXpathEvaluatesTo(EMPLOYEE_5_NAME, "/atom:entry/m:properties/d:EmployeeName", body);
    assertXpathEvaluatesTo(EMPLOYEE_3_NAME,
        "/atom:entry/atom:link[@href=\"Employees('5')/ne_Manager\"]/m:inline/atom:entry/m:properties/d:EmployeeName",
        body);

    response = callUri("Rooms('3')?$expand=nr_Employees/ne_Manager");
    checkMediaType(response, HttpContentType.APPLICATION_ATOM_XML_UTF8 + ";type=entry");
    body = getBody(response);
    assertXpathEvaluatesTo("3", "/atom:entry/atom:content[@type=\"application/xml\"]/m:properties/d:Id", body);
    assertXpathEvaluatesTo("1",
        "count(/atom:entry/atom:link[@href=\"Rooms('3')/nr_Employees\"]/m:inline/atom:feed/atom:entry)", body);
    assertXpathEvaluatesTo(
        EMPLOYEE_5_NAME,
        "/atom:entry/atom:link[@href=\"Rooms('3')/nr_Employees\"]" +
            "/m:inline/atom:feed/atom:entry/m:properties/d:EmployeeName",
        body);
    assertXpathEvaluatesTo(
        EMPLOYEE_3_NAME,
        "/atom:entry/atom:link[@href=\"Rooms('3')/nr_Employees\"]" +
            "/m:inline/atom:feed/atom:entry/atom:link[@href=\"Employees('5')/ne_Manager\"]" +
            "/m:inline/atom:entry/m:properties/d:EmployeeName",
        body);

    notFound("Employees('3')?$expand=noNavProp");
    badRequest("Employees('3')?$expand=Age");
    badRequest("Employees()?$expand=ne_Room/Seats");
    notFound("Employees()?$expand=ne_Room/noNavProp");
  }

  @Test
  public void select() throws Exception {
    HttpResponse response = callUri("Employees('6')?$select=EmployeeId,Age");
    checkMediaType(response, HttpContentType.APPLICATION_ATOM_XML_UTF8 + ";type=entry");
    String body = getBody(response);
    assertXpathEvaluatesTo("6", "/atom:entry/m:properties/d:EmployeeId", body);
    assertXpathEvaluatesTo(EMPLOYEE_6_AGE, "/atom:entry/m:properties/d:Age", body);
    assertXpathNotExists("/atom:entry/m:properties/d:Location", body);
    assertXpathEvaluatesTo("2", "count(/atom:entry/atom:link)", body);

    response = callUri("Employees('3')/ne_Room?$select=Seats");
    checkMediaType(response, HttpContentType.APPLICATION_ATOM_XML_UTF8 + ";type=entry");
    body = getBody(response);
    assertXpathEvaluatesTo("5", "/atom:entry/atom:content/m:properties/d:Seats", body);
    assertXpathNotExists("/atom:entry/m:properties/d:Id", body);

    final String entry = getBody(callUri("Employees('6')"));
    assertEquals(entry, getBody(callUri("Employees('6')?$select=*,Age")));
    assertEquals(entry, getBody(callUri("Employees('6')?$select=*,ne_Room")));

    checkUri("Container2.Photos(Id=4,Type='foo')?$select=%D0%A1%D0%BE%D0%B4%D0%B5%D1%80%D0%B6" +
        "%D0%B0%D0%BD%D0%B8%D0%B5,Id");

    response = callUri("Employees('6')?$expand=ne_Room&$select=ne_Room/Version");
    checkMediaType(response, HttpContentType.APPLICATION_ATOM_XML_UTF8 + ";type=entry");
    body = getBody(response);
    assertXpathEvaluatesTo(
        "2",
        "/atom:entry/atom:link[@href=\"Employees('6')/ne_Room\"]/m:inline/atom:entry/atom:content" +
            "[@type=\"application/xml\"]/m:properties/d:Version",
        body);
    assertXpathNotExists("/atom:entry/m:properties/d:Location", body);
    assertXpathNotExists(
        "/atom:entry/atom:link[@href=\"Employees('6')/ne_Room\"]/m:inline/atom:entry/atom:content" +
            "[@type=\"application/xml\"]/m:properties/d:Seats",
        body);

    response = callUri("Rooms('3')?$expand=nr_Employees/ne_Team&$select=nr_Employees/ne_Team/Name");
    checkMediaType(response, HttpContentType.APPLICATION_ATOM_XML_UTF8 + ";type=entry");
    body = getBody(response);
    assertXpathEvaluatesTo(
        "Team 2",
        "/atom:entry/atom:link[@href=\"Rooms('3')/nr_Employees\"]/m:inline/atom:feed/atom:entry" +
            "/atom:link[@href=\"Employees('5')/ne_Team\"]/m:inline/atom:entry/atom:content/m:properties/d:Name",
        body);
    assertXpathNotExists("/atom:entry/atom:content/m:properties", body);
    assertXpathNotExists(
        "/atom:entry/atom:link[@href=\"Rooms('3')/nr_Employees\"]/m:inline/atom:feed/atom:entry/m:properties", body);
    assertXpathNotExists(
        "/atom:entry/atom:link[@href=\"Rooms('3')/nr_Employees\"]/m:inline/atom:feed/atom:entry" +
            "/atom:link[@href=\"Employees('5')/ne_Team\"]/m:inline/atom:entry/atom:content/m:properties/d:Id",
        body);

    notFound("Teams('3')?$select=noProp");
    notFound("Teams()?$select=nt_Employees/noProp");
    notFound("Employees('3')/ne_Room?$select=Age");
    notFound("Employees('3')/ne_Room?$select=ne_Room");
  }

  @Test
  public void entryMediaResource() throws Exception {
    HttpResponse response = callUri("Employees('2')/$value");
    checkMediaType(response, IMAGE_JPEG);
    assertNotNull(getBody(response));

    response = callUri("Employees('2')/$value?$format=xml", HttpStatusCodes.BAD_REQUEST);
    String body = getBody(response);
    XMLAssert.assertXpathExists("/m:error/m:message", body);
  }

  @Test
  public void navigationEntry() throws Exception {
    HttpResponse response = callUri("Employees('2')/ne_Manager");
    checkMediaType(response, HttpContentType.APPLICATION_ATOM_XML_UTF8 + ";type=entry");
    assertXpathEvaluatesTo(MANAGER_NAME, "/atom:entry/m:properties/d:EmployeeName", getBody(response));

    response = callUri("Employees('2')/ne_Team/nt_Employees('1')");
    checkMediaType(response, HttpContentType.APPLICATION_ATOM_XML_UTF8 + ";type=entry");
    assertXpathEvaluatesTo(EMPLOYEE_1_NAME, "/atom:entry/m:properties/d:EmployeeName", getBody(response));

    response = callUri("Employees('2')/ne_Manager/nm_Employees('6')");
    checkMediaType(response, HttpContentType.APPLICATION_ATOM_XML_UTF8 + ";type=entry");
    assertXpathEvaluatesTo(EMPLOYEE_6_NAME, "/atom:entry/m:properties/d:EmployeeName", getBody(response));

    badRequest("Employees('2')/ne_Manager()");
  }
}
