/**
 *  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.isis.core.runtime.services.memento;

import java.util.List;
import java.util.Set;

import com.google.common.base.Function;
import com.google.common.collect.Iterables;
import com.google.common.collect.Sets;

import org.dom4j.Document;
import org.dom4j.DocumentHelper;
import org.dom4j.Element;

import org.apache.isis.applib.annotation.DomainService;
import org.apache.isis.applib.annotation.NatureOfService;
import org.apache.isis.applib.annotation.Programmatic;
import org.apache.isis.applib.services.memento.MementoService;
import org.apache.isis.applib.services.urlencoding.UrlEncodingService;

/**
 * This service provides a mechanism by which a serializable memento of arbitrary state can be created.  Most
 * commonly this is in support of implementing the {@link org.apache.isis.applib.ViewModel} interface.
 *
 * <p>
 * This implementation has no UI and there are no other implementations of the service API, and so it annotated
 * with {@link org.apache.isis.applib.annotation.DomainService}.  Because this class is implemented in core, this means
 * that it is automatically registered and available for use; no further configuration is required.
 */
@DomainService(
        nature = NatureOfService.DOMAIN,
        menuOrder = "" + Integer.MAX_VALUE
)
public class MementoServiceDefault implements MementoService {

    static class MementoDefault implements Memento {

        private final boolean noEncoding;
        private final Document doc;

        private final UrlEncodingService urlEncodingService;

        MementoDefault(boolean noEncoding, final UrlEncodingService urlEncodingService) {
            this(DocumentHelper.createDocument(), noEncoding, urlEncodingService);
            doc.addElement("memento");
        }

        MementoDefault(
                Document doc,
                boolean noEncoding,
                final UrlEncodingService urlEncodingService) {
            this.doc = doc;
            this.noEncoding = noEncoding;
            this.urlEncodingService = urlEncodingService;
        }
        
        @Override
        public Memento set(String name, Object value) {
            final Element el = doc.getRootElement();
            Dom4jUtil.addChild(el, name, value);
            return this;
        }

        @Override
        public <T> T get(String name, Class<T> cls) {
            final Element el = doc.getRootElement();
            return Dom4jUtil.getChild(el, name, cls);
        }

        @Override
        public String asString() {
            final String xmlStr = Dom4jUtil.asString(doc);
            return encode(xmlStr);
        }

        protected String encode(final String xmlStr) {
            return noEncoding ? xmlStr : urlEncodingService.encode(xmlStr);
        }

        private static final Function<Element, String> ELEMENT_NAME = new Function<Element, String>(){
            @Override
            public String apply(final Element input) {
                return input.getName();
            }
        };

        @Override
        public Set<String> keySet() {
            Element element = doc.getRootElement();
            @SuppressWarnings("unchecked")
            List<Element> elements = element.elements();
            return Sets.newLinkedHashSet(Iterables.transform(elements, ELEMENT_NAME));
        }

        // //////////////////////////////////////

        @Override
        public String toString() {
            return Dom4jUtil.asString(doc);
        }

    }

    // //////////////////////////////////////

    private boolean noEncoding;
    
    public MementoServiceDefault() {
        this.noEncoding = false;
    }

    /**
     * Not public API.
     */
    @Programmatic
    public MementoServiceDefault withNoEncoding() {
        this.noEncoding = true;
        return this;
    }
    
    // //////////////////////////////////////

    @Programmatic
    @Override
    public Memento create() {
        return new MementoDefault(noEncoding, urlEncodingService);
    }


    @Programmatic
    @Override
    public Memento parse(String str) {
        String xmlStr;
        if (noEncoding) {
            xmlStr = str;
        } else {
            xmlStr = urlEncodingService.decode(str);
        }
        final Document doc = Dom4jUtil.parse(xmlStr);
        return new MementoDefault(doc, noEncoding, urlEncodingService);
    }

    @Programmatic
    @Override
    public boolean canSet(final Object input) {
        return input == null || Dom4jUtil.isSupportedClass(input.getClass());
    }

    // //////////////////////////////////////

    @javax.inject.Inject
    UrlEncodingService urlEncodingService;

}
