/*******************************************************************************
 * 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.core.ep.consumer;

import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;

import javax.xml.stream.XMLStreamConstants;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamReader;

import org.apache.olingo.odata2.api.edm.Edm;
import org.apache.olingo.odata2.api.ep.EntityProviderException;
import org.apache.olingo.odata2.api.servicedocument.Accept;
import org.apache.olingo.odata2.api.servicedocument.Categories;
import org.apache.olingo.odata2.api.servicedocument.Category;
import org.apache.olingo.odata2.api.servicedocument.Collection;
import org.apache.olingo.odata2.api.servicedocument.ExtensionAttribute;
import org.apache.olingo.odata2.api.servicedocument.ExtensionElement;
import org.apache.olingo.odata2.api.servicedocument.Fixed;
import org.apache.olingo.odata2.api.servicedocument.Workspace;
import org.apache.olingo.odata2.core.commons.XmlHelper;
import org.apache.olingo.odata2.core.ep.util.FormatXml;
import org.apache.olingo.odata2.core.servicedocument.AcceptImpl;
import org.apache.olingo.odata2.core.servicedocument.AtomInfoImpl;
import org.apache.olingo.odata2.core.servicedocument.CategoriesImpl;
import org.apache.olingo.odata2.core.servicedocument.CategoryImpl;
import org.apache.olingo.odata2.core.servicedocument.CollectionImpl;
import org.apache.olingo.odata2.core.servicedocument.CommonAttributesImpl;
import org.apache.olingo.odata2.core.servicedocument.ExtensionAttributeImpl;
import org.apache.olingo.odata2.core.servicedocument.ExtensionElementImpl;
import org.apache.olingo.odata2.core.servicedocument.ServiceDocumentImpl;
import org.apache.olingo.odata2.core.servicedocument.TitleImpl;
import org.apache.olingo.odata2.core.servicedocument.WorkspaceImpl;

public class AtomServiceDocumentConsumer {
  private String currentHandledStartTagName;
  private static final String DEFAULT_PREFIX = "";

  public ServiceDocumentImpl readServiceDokument(final XMLStreamReader reader) throws EntityProviderException {
    AtomInfoImpl atomInfo = new AtomInfoImpl();
    ServiceDocumentImpl serviceDocument = new ServiceDocumentImpl();
    List<Workspace> workspaces = new ArrayList<Workspace>();
    List<ExtensionElement> extElements = new ArrayList<ExtensionElement>();
    CommonAttributesImpl attributes = new CommonAttributesImpl();
    try {
      while (reader.hasNext()
          && !(reader.isEndElement() && Edm.NAMESPACE_APP_2007.equals(reader.getNamespaceURI()) && FormatXml.APP_SERVICE
              .equals(reader.getLocalName()))) {
        reader.next();
        if (reader.isStartElement()) {
          currentHandledStartTagName = reader.getLocalName();
          if (FormatXml.APP_SERVICE.equals(currentHandledStartTagName)) {
            attributes = parseCommonAttribute(reader);
          } else if (FormatXml.APP_WORKSPACE.equals(currentHandledStartTagName)) {
            workspaces.add(parseWorkspace(reader));
          } else {
            ExtensionElementImpl extElement = parseExtensionElement(reader);
            if (extElement != null) {
              extElements.add(extElement);
            }
          }
        }
      }
      if (workspaces.isEmpty()) {
        throw new EntityProviderException(EntityProviderException.INVALID_STATE
            .addContent("Service element must contain at least one workspace element"));
      }
      reader.close();
      atomInfo.setWorkspaces(workspaces).setCommonAttributes(attributes).setExtesionElements(extElements);

      serviceDocument.setAtomInfo(atomInfo);
      serviceDocument.setEntitySetsInfo(atomInfo.getEntitySetsInfo());
      return serviceDocument;
    } catch (XMLStreamException e) {
      throw new EntityProviderException(EntityProviderException.EXCEPTION_OCCURRED.addContent(e.getClass()
          .getSimpleName()), e);
    }
  }

  private CommonAttributesImpl parseCommonAttribute(final XMLStreamReader reader) {
    CommonAttributesImpl attribute = new CommonAttributesImpl();
    List<ExtensionAttribute> extAttributes = new ArrayList<ExtensionAttribute>();
    attribute.setBase(reader.getAttributeValue(null, FormatXml.XML_BASE));
    attribute.setLang(reader.getAttributeValue(null, FormatXml.XML_LANG));
    for (int i = 0; i < reader.getAttributeCount(); i++) {
      if (!(FormatXml.XML_BASE.equals(reader.getAttributeLocalName(i))
          && Edm.PREFIX_XML.equals(reader.getAttributePrefix(i))
          || (FormatXml.XML_LANG.equals(reader.getAttributeLocalName(i)) && Edm.PREFIX_XML.equals(reader
              .getAttributePrefix(i)))
          || ("local".equals(reader.getAttributeNamespace(i)) || DEFAULT_PREFIX.equals(reader.getAttributePrefix(i)))))
      {
        extAttributes.add(new ExtensionAttributeImpl()
            .setName(reader.getAttributeLocalName(i))
            .setNamespace(reader.getAttributeNamespace(i))
            .setPrefix(reader.getAttributePrefix(i))
            .setText(reader.getAttributeValue(i)));
      }
    }

    return attribute.setAttributes(extAttributes);
  }

  private WorkspaceImpl parseWorkspace(final XMLStreamReader reader)
      throws XMLStreamException, EntityProviderException {
    reader.require(XMLStreamConstants.START_ELEMENT, Edm.NAMESPACE_APP_2007, FormatXml.APP_WORKSPACE);

    TitleImpl title = null;
    List<Collection> collections = new ArrayList<Collection>();
    List<ExtensionElement> extElements = new ArrayList<ExtensionElement>();
    CommonAttributesImpl attributes = parseCommonAttribute(reader);
    while (reader.hasNext()
        && !(reader.isEndElement() && Edm.NAMESPACE_APP_2007.equals(reader.getNamespaceURI()) && FormatXml.APP_WORKSPACE
            .equals(reader.getLocalName()))) {
      reader.next();
      if (reader.isStartElement()) {
        currentHandledStartTagName = reader.getLocalName();
        if (FormatXml.APP_COLLECTION.equals(currentHandledStartTagName)) {
          collections.add(parseCollection(reader));
        } else if (FormatXml.ATOM_TITLE.equals(currentHandledStartTagName)) {
          title = parseTitle(reader);
        } else {
          extElements.add(parseExtensionSansTitleElement(reader));
        }
      }
    }
    if (title == null) {
      throw new EntityProviderException(EntityProviderException.INVALID_STATE
          .addContent("Missing element title for workspace"));
    }
    return new WorkspaceImpl().setTitle(title).setCollections(collections).setAttributes(attributes)
        .setExtesionElements(extElements);
  }

  private CollectionImpl parseCollection(final XMLStreamReader reader) throws XMLStreamException,
      EntityProviderException {
    reader.require(XMLStreamConstants.START_ELEMENT, Edm.NAMESPACE_APP_2007, FormatXml.APP_COLLECTION);
    TitleImpl title = null;
    String resourceIdentifier = reader.getAttributeValue(null, FormatXml.ATOM_HREF);
    CommonAttributesImpl attributes = parseCommonAttribute(reader);
    List<ExtensionElement> extElements = new ArrayList<ExtensionElement>();
    List<Accept> acceptList = new ArrayList<Accept>();
    List<Categories> categories = new ArrayList<Categories>();
    if (resourceIdentifier == null) {
      throw new EntityProviderException(EntityProviderException.MISSING_ATTRIBUTE.addContent("href"));
    }
    while (reader.hasNext()
        && !(reader.isEndElement() && Edm.NAMESPACE_APP_2007.equals(reader.getNamespaceURI())
        && FormatXml.APP_COLLECTION.equals(reader.getLocalName()))) {
      reader.next();
      if (reader.isStartElement()) {
        currentHandledStartTagName = reader.getLocalName();
        if (FormatXml.ATOM_TITLE.equals(currentHandledStartTagName)) {
          title = parseTitle(reader);
        } else if (FormatXml.APP_ACCEPT.equals(currentHandledStartTagName)) {
          acceptList.add(parseAccept(reader));
        } else if (FormatXml.APP_CATEGORIES.equals(currentHandledStartTagName)) {
          categories.add(parseCategories(reader));
        } else {
          extElements.add(parseExtensionSansTitleElement(reader));
        }
      }
    }
    return new CollectionImpl().setHref(resourceIdentifier).setTitle(title).setCommonAttributes(attributes)
        .setExtesionElements(extElements).setAcceptElements(acceptList).setCategories(categories);
  }

  private TitleImpl parseTitle(final XMLStreamReader reader) throws XMLStreamException {
    reader.require(XMLStreamConstants.START_ELEMENT, Edm.NAMESPACE_ATOM_2005, FormatXml.ATOM_TITLE);
    String text = reader.getElementText();
    reader.require(XMLStreamConstants.END_ELEMENT, Edm.NAMESPACE_ATOM_2005, FormatXml.ATOM_TITLE);
    return new TitleImpl().setText(text);
  }

  private AcceptImpl parseAccept(final XMLStreamReader reader) throws XMLStreamException {
    reader.require(XMLStreamConstants.START_ELEMENT, Edm.NAMESPACE_APP_2007, FormatXml.APP_ACCEPT);
    CommonAttributesImpl commonAttributes = parseCommonAttribute(reader);
    String text = reader.getElementText();
    reader.require(XMLStreamConstants.END_ELEMENT, Edm.NAMESPACE_APP_2007, FormatXml.APP_ACCEPT);
    return new AcceptImpl().setCommonAttributes(commonAttributes).setText(text);
  }

  private CategoriesImpl parseCategories(final XMLStreamReader reader) throws XMLStreamException,
      EntityProviderException {
    reader.require(XMLStreamConstants.START_ELEMENT, Edm.NAMESPACE_APP_2007, FormatXml.APP_CATEGORIES);
    CategoriesImpl categories = new CategoriesImpl();
    String href = reader.getAttributeValue(null, FormatXml.ATOM_HREF);
    String fixed = reader.getAttributeValue(null, FormatXml.APP_CATEGORIES_FIXED);
    categories.setScheme(reader.getAttributeValue(null, FormatXml.APP_CATEGORIES_SCHEME));
    categories.setHref(href);
    if (href == null) {
      for (int i = 0; i < Fixed.values().length; i++) {
        if (Fixed.values()[i].name().equalsIgnoreCase(fixed)) {
          categories.setFixed(Fixed.values()[i]);
        }
      }
      if (categories.getFixed() == null) {
        categories.setFixed(Fixed.NO);
      }
      List<Category> categoriesList = new ArrayList<Category>();
      while (reader.hasNext()
          && !(reader.isEndElement() && Edm.NAMESPACE_APP_2007.equals(reader.getNamespaceURI())
          && FormatXml.APP_CATEGORIES.equals(reader.getLocalName()))) {
        reader.next();
        if (reader.isStartElement()) {
          currentHandledStartTagName = reader.getLocalName();
          if (FormatXml.ATOM_CATEGORY.equals(currentHandledStartTagName)) {
            categoriesList.add(parseCategory(reader));
          }
        }
      }
      categories.setCategoryList(categoriesList);
    }
    if ((href != null && fixed != null && categories.getScheme() != null) ||
        (href == null && fixed == null && categories.getScheme() == null)) {
      throw new EntityProviderException(EntityProviderException.MISSING_ATTRIBUTE
          .addContent("for the element categories"));
    }
    return categories;
  }

  private CategoryImpl parseCategory(final XMLStreamReader reader) throws XMLStreamException {
    reader.require(XMLStreamConstants.START_ELEMENT, Edm.NAMESPACE_ATOM_2005, FormatXml.ATOM_CATEGORY);
    CategoryImpl category = new CategoryImpl();
    category.setScheme(reader.getAttributeValue(null, FormatXml.ATOM_CATEGORY_SCHEME));
    category.setTerm(reader.getAttributeValue(null, FormatXml.ATOM_CATEGORY_TERM));
    category.setLabel(reader.getAttributeValue(null, FormatXml.ATOM_CATEGORY_LABEL));
    CommonAttributesImpl attributes = parseCommonAttribute(reader);
    return category.setCommonAttributes(attributes);
  }

  private ExtensionElementImpl parseExtensionSansTitleElement(final XMLStreamReader reader) throws XMLStreamException,
      EntityProviderException {
    ExtensionElementImpl extElement = new ExtensionElementImpl();
    if (!(Edm.NAMESPACE_APP_2007.equals(reader.getNamespaceURI())
    || (FormatXml.ATOM_TITLE.equals(reader.getLocalName())
    && Edm.NAMESPACE_ATOM_2005.equals(reader.getNamespaceURI())))) {
      extElement = parseElement(reader);
    }
    return extElement;
  }

  private ExtensionElementImpl parseExtensionElement(final XMLStreamReader reader) throws XMLStreamException,
      EntityProviderException {
    ExtensionElementImpl extElement = null;
    if (!Edm.NAMESPACE_APP_2007.equals(reader.getNamespaceURI())) {
      extElement = parseElement(reader);
    }
    return extElement;
  }

  private ExtensionElementImpl parseElement(final XMLStreamReader reader) throws XMLStreamException,
      EntityProviderException {
    List<ExtensionElement> extensionElements = new ArrayList<ExtensionElement>();
    ExtensionElementImpl extElement =
        new ExtensionElementImpl().setName(reader.getLocalName()).setNamespace(reader.getNamespaceURI()).setPrefix(
            reader.getPrefix());
    extElement.setAttributes(parseAttribute(reader));
    while (reader.hasNext()
        && !(reader.isEndElement() && extElement.getName() != null && extElement.getName()
            .equals(reader.getLocalName()))) {
      reader.next();
      if (reader.isStartElement()) {
        extensionElements.add(parseExtensionElement(reader));
      } else if (reader.isCharacters()) {
        String extElementText = "";
        do {
          extElementText = extElementText + reader.getText();
          reader.next();
        } while (reader.isCharacters());
        extElement.setText(extElementText);
      }
    }
    extElement.setElements(extensionElements);
    if (extElement.getText() == null && extElement.getAttributes().isEmpty() && extElement.getElements().isEmpty()) {
      throw new EntityProviderException(EntityProviderException.INVALID_STATE.addContent("Invalid extension element"));
    }
    return extElement;
  }

  private List<ExtensionAttribute> parseAttribute(final XMLStreamReader reader) {
    List<ExtensionAttribute> extAttributes = new ArrayList<ExtensionAttribute>();
    for (int i = 0; i < reader.getAttributeCount(); i++) {
      {
        extAttributes.add(new ExtensionAttributeImpl()
            .setName(reader.getAttributeLocalName(i))
            .setNamespace(reader.getAttributeNamespace(i))
            .setPrefix(reader.getAttributePrefix(i))
            .setText(reader.getAttributeValue(i)));
      }
    }

    return extAttributes;
  }

  public ServiceDocumentImpl parseXml(final InputStream in) throws EntityProviderException {
    return readServiceDokument(XmlHelper.createStreamReader(in));
  }
}
