/*
 * JBoss, Home of Professional Open Source.
 * Copyright 2016 Red Hat, Inc., and individual contributors
 * as indicated by the @author tags.
 *
 * 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.keycloak.authorization.attribute;

import java.net.InetAddress;
import java.net.UnknownHostException;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Collection;
import java.util.Date;
import java.util.Map;

import static java.util.Collections.emptyList;

/**
 * <p>Holds attributes, their values and provides utlity methods to manage them.
 *
 * <p>In the future, it may be useful to provide different implementations for this interface in order to plug or integrate with different
 * Policy Information Point (PIP).</p>
 *
 * @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
 */
public interface Attributes {

    static Attributes from(Map<String, Collection<String>> attributes) {
        return () -> attributes;
    }

    /**
     * Converts to a {@link Map}.
     *
     * @return
     */
    Map<String, Collection<String>> toMap();

    /**
     * Checks if there is an attribute with the given <code>name</code>.
     *
     * @param name the attribute name
     * @return true if any attribute with <code>name</code> exist. Otherwise, returns false.
     */
    default boolean exists(String name) {
        return toMap().containsKey(name);
    }

    /**
     * Checks if there is an attribute with the given <code>name</code> and <code>value</code>.
     *
     * @param name the attribute name
     * @param value the attribute value
     * @return true if any attribute with <code>name</code> and <code>value</code> exist. Otherwise, returns false.
     */
    default boolean containsValue(String name, String value) {
        return toMap().getOrDefault(name, emptyList()).stream().anyMatch(value::equals);
    }

    /**
     * Returns a {@link Entry} from where values can be obtained and parsed accordingly.
     *
     * @param name the attribute name
     * @return an {@link Entry} holding the values for an attribute
     */
    default Entry getValue(String name) {
        Collection<String> value = toMap().get(name);

        if (value != null) {
            return new Entry(name, value);
        }

        return null;
    }

    /**
     * Holds an attribute and its values, providing useful methods for obtaining and formatting values. Specially useful
     * for writing rule-based policies.
     */
    class Entry {

        private final String[] values;
        private final String name;

        Entry(String name, Collection<String> values) {
            this.name = name;
            this.values = values.toArray(new String[values.size()]);
        }

        private String getName() {
            return this.name;
        }

        public int size() {
            return values.length;
        }

        public String asString(int idx) {
            if (idx >= values.length) {
                throw new IllegalArgumentException("Invalid index [" + idx + "]. Values are [" + values + "].");
            }

            return values[idx];
        }

        public int asInt(int idx) {
            return Integer.parseInt(asString(idx));
        }

        public Date asDate(int idx, String pattern) {
            try {
                return new SimpleDateFormat(pattern).parse(asString(idx));
            } catch (ParseException e) {
                throw new RuntimeException("Error parsing date.", e);
            }
        }

        public InetAddress asInetAddress(int idx) {
            try {
                return InetAddress.getByName(asString(idx));
            } catch (UnknownHostException e) {
                throw new RuntimeException("Error parsing address.", e);
            }
        }

        public long asLong(int idx) {
            return Long.parseLong(asString(idx));
        }
    }
}
