/*
 * Copyright 2004-2012 the Seasar Foundation and the Others.
 *
 * Licensed 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.seasar.mayaa.impl.builder.library;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.seasar.mayaa.builder.library.LibraryDefinition;
import org.seasar.mayaa.builder.library.ProcessorDefinition;
import org.seasar.mayaa.builder.library.PropertyDefinition;
import org.seasar.mayaa.builder.library.PropertySet;
import org.seasar.mayaa.builder.library.converter.PropertyConverter;
import org.seasar.mayaa.engine.processor.InformalPropertyAcceptable;
import org.seasar.mayaa.engine.processor.TemplateProcessor;
import org.seasar.mayaa.engine.processor.VirtualPropertyAcceptable;
import org.seasar.mayaa.engine.specification.NodeAttribute;
import org.seasar.mayaa.engine.specification.PrefixAwareName;
import org.seasar.mayaa.engine.specification.QName;
import org.seasar.mayaa.engine.specification.SpecificationNode;
import org.seasar.mayaa.engine.specification.URI;
import org.seasar.mayaa.impl.CONST_IMPL;
import org.seasar.mayaa.impl.engine.specification.SpecificationUtil;
import org.seasar.mayaa.impl.util.ObjectUtil;
import org.seasar.mayaa.impl.util.StringUtil;
import org.seasar.mayaa.impl.util.collection.NullIterator;

/**
 * @author Masataka Kurihara (Gluegent, Inc.)
 */
public class ProcessorDefinitionImpl extends PropertySetImpl
        implements ProcessorDefinition, CONST_IMPL {

    private static final long serialVersionUID = -9158405427849768423L;
    private static final Log LOG =
        LogFactory.getLog(ProcessorDefinitionImpl.class);

    private Class _processorClass;
    private List _propertySetRefs;

    public void setProcessorClass(Class processorClass) {
        if (processorClass == null
                || TemplateProcessor.class.isAssignableFrom(
                        processorClass) == false) {
            throw new IllegalArgumentException();
        }
        _processorClass = processorClass;
    }

    public Class getProcessorClass() {
        return _processorClass;
    }

    public void addPropertySetRef(
            String name, String systemID, int lineNumber) {
        if (StringUtil.isEmpty(name)) {
            throw new IllegalArgumentException();
        }
        if (_propertySetRefs == null) {
            _propertySetRefs = new ArrayList();
        }
        if (_propertySetRefs.contains(name)) {
            if (LOG.isWarnEnabled()) {
                String line = Integer.toString(lineNumber);
                LOG.warn(StringUtil.getMessage(
                        ProcessorDefinitionImpl.class, 1, name, systemID, line));
            }
        } else {
            _propertySetRefs.add(
                    new PropertySetRef(name, systemID, lineNumber));
        }
    }

    public Iterator iteratePropertySets() {
        if (_propertySetRefs == null) {
            return NullIterator.getInstance();
        }
        Iterator it = _propertySetRefs.iterator();
        return new PropertySetIterator(it, getLibraryDefinition());
    }

    protected TemplateProcessor newInstance() {
        return (TemplateProcessor) ObjectUtil.newInstance(_processorClass);
    }

    protected PrefixAwareName getPrefixAwareName(
            SpecificationNode injected, String propertyName) {
        QName qName = SpecificationUtil.createQName(
                injected.getQName().getNamespaceURI(), propertyName);
        // メモリリークを起こす可能性があるので参照を断つ
        NodeAttribute attr = injected.getAttribute(qName);
        PrefixAwareName name = SpecificationUtil.createPrefixAwareName(
                attr.getQName(), attr.getPrefix());
        return name;
    }

    protected void settingProperty(
            SpecificationNode original, SpecificationNode injected,
            TemplateProcessor processor, PropertyDefinition property) {
        Object value =
            property.createProcessorProperty(this, processor, original, injected);
        if (value != null) {
            String propertyImplName = property.getImplName();
            Class processorClass = getProcessorClass();
            if (ObjectUtil.hasProperty(processorClass, propertyImplName)) {
                // prefixが付いていない限りoriginalを引き継がせる
                if (value instanceof PrefixAwareName) {
                    value = settingDefaultNamespace(
                            original, (PrefixAwareName) value);
                }
                ObjectUtil.setProperty(processor, propertyImplName, value);
            } else if (processor instanceof VirtualPropertyAcceptable) {
                VirtualPropertyAcceptable acceptable =
                    (VirtualPropertyAcceptable) processor;
                if (value instanceof Serializable) {
                    PrefixAwareName name =
                        getPrefixAwareName(injected, property.getName());
                    acceptable.addVirtualProperty(name, (Serializable) value);
                } else {
                    if (LOG.isWarnEnabled()) {
                        LOG.warn(StringUtil.getMessage(
                                ProcessorDefinitionImpl.class, 3,
                                processorClass.getName(), propertyImplName,
                                value.getClass().getName()));
                    }
                }
            } else {
                if (LOG.isWarnEnabled()) {
                    LOG.warn(StringUtil.getMessage(
                            ProcessorDefinitionImpl.class, 2,
                            processorClass.getName(), propertyImplName));
                }
            }
        }
    }

    protected PrefixAwareName settingDefaultNamespace(
            SpecificationNode original, PrefixAwareName value) {
        QName valueName = value.getQName();
        if (URI_MAYAA.equals(valueName.getNamespaceURI())) {
            if (StringUtil.isEmpty(value.getPrefix())) {
                QName qName = SpecificationUtil.createQName(
                        original.getDefaultNamespaceURI(),
                        valueName.getLocalName());
                value = SpecificationUtil.createPrefixAwareName(qName, "");
            }
        }
        return value;
    }

    protected void settingPropertySet(
            SpecificationNode original, SpecificationNode injected,
            TemplateProcessor processor, PropertySet propertySet) {
        for (Iterator it = propertySet.iteratePropertyDefinition(); it.hasNext();) {
            PropertyDefinition property = (PropertyDefinition) it.next();
            settingProperty(original, injected, processor, property);
        }
    }

    protected void settingInformalProperties(SpecificationNode injected,
            InformalPropertyAcceptable acceptable) {
        URI injectedNS = injected.getQName().getNamespaceURI();
        for (Iterator it = injected.iterateAttribute(); it.hasNext();) {
            NodeAttribute attr = (NodeAttribute) it.next();
            if (contain(injectedNS, attr)) {
                continue;
            }
            LibraryDefinition library = getLibraryDefinition();
            Class propertyClass = acceptable.getInformalPropertyClass();
            PropertyConverter converter =
                library.getPropertyConverter(propertyClass);
            if (converter == null) {
                throw new ConverterNotFoundException(
                        propertyClass.getName(), getSystemID(), getLineNumber());
            }
            Class expectedClass = acceptable.getInformalExpectedClass();
            String value = attr.getValue();
            Serializable property = converter.convert(attr, value, expectedClass);
            if (property == null) {
                throw new ConverterOperationException(converter, value);
            }
            // メモリリークを起こす可能性があるので参照を断つ
            PrefixAwareName name = SpecificationUtil.createPrefixAwareName(
                    attr.getQName(), attr.getPrefix());
            acceptable.addInformalProperty(name, property);
        }
    }

    public TemplateProcessor createTemplateProcessor(
            SpecificationNode original, SpecificationNode injected) {
        if (injected == null) {
            throw new IllegalArgumentException();
        }
        TemplateProcessor processor = newInstance();
        processor.setProcessorDefinition(this);
        settingPropertySet(original, injected, processor, this);
        for (Iterator it = iteratePropertySets(); it.hasNext();) {
            PropertySet propertySet = (PropertySet) it.next();
            settingPropertySet(original, injected, processor, propertySet);
        }
        if (processor instanceof InformalPropertyAcceptable) {
            settingInformalProperties(
                    injected, (InformalPropertyAcceptable) processor);
        }
        return processor;
    }

    // support class ------------------------------------------------

    protected static class PropertySetRef implements Serializable {

        private static final long serialVersionUID = -3425332447146558868L;

        private String _name;
        private String _systemID;
        private int _lineNumber;

        public PropertySetRef(String name, String systemID, int lineNumber) {
            if (StringUtil.isEmpty(name)
                    || StringUtil.isEmpty(systemID) || lineNumber < 0) {
                throw new IllegalArgumentException();
            }
            _name = name;
            _systemID = systemID;
            _lineNumber = lineNumber;
        }

        public String getName() {
            return _name;
        }

        public String getSystemID() {
            return _systemID;
        }

        public int getLineNumber() {
            return _lineNumber;
        }

    }

    protected static class PropertySetIterator implements Iterator {

        private Iterator _it;
        private LibraryDefinition _library;

        public PropertySetIterator(Iterator it, LibraryDefinition library) {
            if (it == null || library == null) {
                throw new IllegalArgumentException();
            }
            _it = it;
            _library = library;
        }

        public boolean hasNext() {
            return _it.hasNext();
        }

        public Object next() {
            PropertySetRef ref = (PropertySetRef) _it.next();
            PropertySet propertySet = _library.getPropertySet(ref.getName());
            if (propertySet == null) {
                throw new PropertySetNotFoundException(ref.getName(),
                        ref.getSystemID(), ref.getLineNumber());
            }
            return propertySet;
        }

        public void remove() {
            throw new UnsupportedOperationException();
        }

    }

}
