/*
 * Copyright 2005 Red Hat, Inc. and/or its affiliates.
 *
 * 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.drools.core.rule;

import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.drools.core.base.ClassObjectType;
import org.drools.core.rule.accessor.PatternExtractor;

public class XpathBackReference {
    public static final String BACK_REFERENCE_HEAD = "$back$ref$";

    private final Pattern pattern;
    private final List<Class<?>> backReferenceClasses;

    private Map<String, Declaration> declarations = Collections.emptyMap();

    private MapAdapter declarationMap;

    public XpathBackReference( Pattern pattern, List<Class<?>> backReferenceClasses ) {
        this.pattern = pattern;
        this.backReferenceClasses = backReferenceClasses;
    }

    public void reset() {
        declarations.clear();
    }

    public List<Class<?>> getBackReferenceClasses() {
        return backReferenceClasses;
    }

    public Declaration getDeclaration(Pattern pattern, String id) {
        if (!id.startsWith( BACK_REFERENCE_HEAD )) {
            return null;
        }
        Declaration declaration = declarations.get(id);
        if (declaration != null) {
            return declaration;
        }

        int backRefPos = Integer.parseInt( id.substring( XpathBackReference.BACK_REFERENCE_HEAD.length() ) );
        int relativeOffset = backReferenceClasses.size() - 1 - backRefPos;
        declaration = new Declaration(id, new PatternExtractor( new ClassObjectType( backReferenceClasses.get(backRefPos))), pattern, true);
        declaration.setxPathOffset(relativeOffset);

        if (declarations.isEmpty()) {
            declarations = new HashMap<>();
        }

        declarations.put( id, declaration );
        return declaration;
    }

    public Map<String, Declaration> getDeclarationMap() {
        if (declarationMap == null) {
            declarationMap = new MapAdapter();
        }
        return declarationMap;
    }

    public static class RelativePattern extends Pattern {
        private final Pattern pattern;
        private final int relativeOffset;

        public RelativePattern( Pattern pattern, int relativeOffset ) {
            this.pattern = pattern;
            this.relativeOffset = relativeOffset;
        }

        @Override
        public int getTupleIndex() {
            return pattern.getTupleIndex() + relativeOffset;
        }
    }

    public class MapAdapter implements Map<String, Declaration> {

        @Override
        public int size() {
            return pattern.getDeclarations().size() + declarations.size();
        }

        @Override
        public boolean isEmpty() {
            return pattern.getDeclarations().isEmpty() && declarations.isEmpty();
        }

        @Override
        public boolean containsKey( Object key ) {
            return pattern.getDeclarations().containsKey(key) || declarations.containsKey(key);
        }

        @Override
        public boolean containsValue( Object value ) {
            return pattern.getDeclarations().containsValue(value) || declarations.containsValue( value );
        }

        @Override
        public Declaration get( Object key ) {
            Declaration declaration = pattern.getDeclarations().get(key);
            if (declaration == null) {
                declaration = getDeclaration(pattern, (String)key);
            }
            return declaration;
        }

        @Override
        public Declaration put( String key, Declaration value ) {
            return pattern.getDeclarations().put( key, value );
        }

        @Override
        public Declaration remove( Object key ) {
            return pattern.getDeclarations().remove( key );
        }

        @Override
        public void putAll( Map<? extends String, ? extends Declaration> m ) {
            pattern.getDeclarations().putAll(m);
        }

        @Override
        public void clear() {
            pattern.getDeclarations().clear();
            declarations.clear();
        }

        @Override
        public Set<String> keySet() {
            return pattern.getDeclarations().keySet();
        }

        @Override
        public Collection<Declaration> values() {
            return pattern.getDeclarations().values();
        }

        @Override
        public Set<Entry<String, Declaration>> entrySet() {
            return pattern.getDeclarations().entrySet();
        }
    }
}
