/*
 * 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.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.seasar.mayaa.PositionAware;
import org.seasar.mayaa.builder.library.DefinitionBuilder;
import org.seasar.mayaa.builder.library.LibraryDefinition;
import org.seasar.mayaa.builder.library.LibraryManager;
import org.seasar.mayaa.builder.library.ProcessorDefinition;
import org.seasar.mayaa.builder.library.converter.PropertyConverter;
import org.seasar.mayaa.builder.library.scanner.SourceScanner;
import org.seasar.mayaa.engine.specification.QName;
import org.seasar.mayaa.engine.specification.URI;
import org.seasar.mayaa.impl.ParameterAwareImpl;
import org.seasar.mayaa.impl.builder.library.scanner.SourceAlias;
import org.seasar.mayaa.impl.builder.library.scanner.WebXMLTaglibSourceScanner;
import org.seasar.mayaa.impl.engine.specification.SpecificationUtil;
import org.seasar.mayaa.impl.util.StringUtil;
import org.seasar.mayaa.impl.util.collection.AbstractScanningIterator;
import org.seasar.mayaa.source.SourceDescriptor;

/**
 * @author Masataka Kurihara (Gluegent, Inc.)
 */
public class LibraryManagerImpl extends ParameterAwareImpl
        implements LibraryManager {

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

    private List _scanners;
    private List _builders;
    private List _libraries;
    private Map _converters;

    public LibraryManagerImpl() {
        _scanners = new ArrayList();
        _builders = new ArrayList();
        _converters = new HashMap();
    }

    protected void warnAlreadyRegistered(
            PositionAware obj, String name, int index) {
        if (LOG.isWarnEnabled()) {
            String systemID = obj.getSystemID();
            String lineNumber = Integer.toString(obj.getLineNumber());
            LOG.warn(StringUtil.getMessage(LibraryManagerImpl.class, index,
                    name, systemID, lineNumber));
        }
    }

    public void addPropertyConverter(
            String name, PropertyConverter propertyConverter) {
        if (propertyConverter == null) {
            throw new IllegalArgumentException();
        }
        if (StringUtil.isEmpty(name)) {
            name = propertyConverter.getPropetyClass().getName();
        }
        if (_converters.containsKey(name)) {
            warnAlreadyRegistered(propertyConverter, name, 1);
        } else {
            _converters.put(name, propertyConverter);
        }
    }

    public PropertyConverter getPropertyConverter(String converterName) {
        if (StringUtil.isEmpty(converterName)) {
            throw new IllegalArgumentException();
        }
        return (PropertyConverter) _converters.get(converterName);
    }

    public PropertyConverter getPropertyConverter(Class propertyClass) {
        if (propertyClass == null) {
            throw new IllegalArgumentException();
        }
        for (Iterator it = _converters.values().iterator(); it.hasNext();) {
            PropertyConverter propertyConverter = (PropertyConverter) it.next();
            Class converterClass = propertyConverter.getPropetyClass();
            if (propertyClass.isAssignableFrom(converterClass)) {
                return propertyConverter;
            }
        }
        return null;
    }

    public Iterator iteratePropertyConverters() {
        return _converters.values().iterator();
    }

    public void addSourceScanner(SourceScanner scanner) {
        if (scanner == null) {
            throw new IllegalArgumentException();
        }
        synchronized (_scanners) {
            if (_scanners.contains(scanner)) {
                warnAlreadyRegistered(scanner, scanner.getClass().getName(), 1);
            } else {
                _scanners.add(scanner);
            }
        }
    }

    public void addDefinitionBuilder(DefinitionBuilder builder) {
        if (builder == null) {
            throw new IllegalArgumentException();
        }
        synchronized (_builders) {
            if (_builders.contains(builder)) {
                warnAlreadyRegistered(builder, builder.getClass().getName(), 3);
            } else {
                _builders.add(builder);
            }
        }
    }

    public void prepareLibraries() {
        if (_libraries == null) {
            buildAll();
        }
    }

    protected void buildAll() {
        _libraries = new ArrayList();
        Set builtLibraries = new HashSet();
        for (int i = 0; i < _scanners.size(); i++) {
            SourceScanner scanner = (SourceScanner) _scanners.get(i);
            for (Iterator it = scanner.scan(); it.hasNext();) {
                SourceDescriptor source = (SourceDescriptor) it.next();
                boolean built = false;
                for (int k = 0; k < _builders.size(); k++) {
                    DefinitionBuilder builder =
                        (DefinitionBuilder) _builders.get(k);
                    LibraryDefinition library = builder.build(source);
                    if (library != null) {
                        if (builtLibraries.contains(
                                library.getNamespaceURI()) == false) {
                            _libraries.add(library);
                            builtLibraries.add(library.getNamespaceURI());
                            if (LOG.isInfoEnabled()) {
                                LOG.info(StringUtil.getMessage(
                                    LibraryManagerImpl.class, 4,
                                    source.getSystemID(),
                                    String.valueOf(library.getNamespaceURI())));
                            }
                        }
                        built = true;
                        break;
                    }
                }

                if (built == false) {
                    assignTaglibLocation(source);
                }
            }
        }
    }

    // condition: already loaded "META-INF/taglib.tld"
    private void assignTaglibLocation(SourceDescriptor source) {
        String realPath =
            source.getParameter(WebXMLTaglibSourceScanner.REAL_PATH);
        if (StringUtil.isEmpty(realPath)
                || realPath.endsWith(".jar") == false) {
            return;
        }

        for (int j = 0; j < _libraries.size(); j++) {
            LibraryDefinition library = (LibraryDefinition) _libraries.get(j);
            for (Iterator it = library.iterateAssignedURI(); it.hasNext();) {
                URI uri = (URI) it.next();
                if (realPath.equals(String.valueOf(uri))) {
                    URI assignedURI = SpecificationUtil.createURI(
                            source.getParameter(SourceAlias.ALIAS));
                    library.addAssignedURI(assignedURI);
                    if (LOG.isInfoEnabled()) {
                        LOG.info(StringUtil.getMessage(
                            LibraryManagerImpl.class, 4,
                            library.getNamespaceURI() + " (alias)",
                            String.valueOf(assignedURI)));
                    }
                    return;
                }
            }
        }
    }

    public Iterator iterateLibraryDefinition() {
        prepareLibraries();
        return _libraries.iterator();
    }

    public Iterator iterateLibraryDefinition(URI namespaceURI) {
        if (StringUtil.isEmpty(namespaceURI)) {
            throw new IllegalArgumentException();
        }
        return new LibraryDefinitionFilteredIterator(
                namespaceURI, iterateLibraryDefinition());
    }

    public ProcessorDefinition getProcessorDefinition(QName qName) {
        if (qName == null) {
            throw new IllegalArgumentException();
        }
        URI namespaceURI = qName.getNamespaceURI();
        String localName = qName.getLocalName();
        for (Iterator it = iterateLibraryDefinition(namespaceURI);
                it.hasNext();)  {
            LibraryDefinition library = (LibraryDefinition) it.next();
            ProcessorDefinition processor =
                library.getProcessorDefinition(localName);
            if (processor != null) {
                return processor;
            }
        }
        return null;
    }

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

    private static class LibraryDefinitionFilteredIterator
            extends AbstractScanningIterator {

        private URI _namespaceURI;

        public LibraryDefinitionFilteredIterator(
                URI namespaceURI, Iterator iterator) {
            super(iterator);
            if (StringUtil.isEmpty(namespaceURI)) {
                throw new IllegalArgumentException();
            }
            _namespaceURI = namespaceURI;
        }

        protected boolean filter(Object test) {
            if (test == null || (test instanceof LibraryDefinition == false)) {
                return false;
            }
            LibraryDefinition library = (LibraryDefinition) test;
            if (_namespaceURI.equals(library.getNamespaceURI())) {
                return true;
            }
            for (Iterator it = library.iterateAssignedURI(); it.hasNext();) {
                if (_namespaceURI.equals(it.next())) {
                    return true;
                }
            }
            return false;
        }

    }

}
