/**
 * JBoss, Home of Professional Open Source
 * Copyright Red Hat, Inc., and individual contributors.
 *
 * 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.jboss.aerogear.unifiedpush.message.configuration;

import javax.enterprise.context.ApplicationScoped;
import javax.enterprise.inject.Produces;

import org.jboss.aerogear.unifiedpush.api.VariantType;
import org.jboss.aerogear.unifiedpush.message.sender.SenderType;
import org.jboss.aerogear.unifiedpush.message.util.ConfigurationUtils;
import org.jboss.aerogear.unifiedpush.utils.AeroGearLogger;

/**
 * Loads and stores configuration for specific Push Networks.
 *
 * UPS provides sensible defaults specific to each Push Network and its limitations.
 *
 * Configuration can be changed by overriding system properties, e.g. via as VM args.
 *
 * Example: In order to change the configuration of {@link SenderConfiguration#batchSize()} for APNs (iOS) service,
 * one can provide following system property: <tt>-Daerogear.ios.batchSize=12000</tt>.
 *
 * The name of the network (<tt>aerogear.&lt;network&gt;.&lt;property&gt;</tt>) is specified by {@link VariantType#getTypeName()}.
 *
 * Look at {@link SenderConfiguration} for more details about available configurations.
 *
 * @see SenderConfiguration
 */
public class SenderConfigurationProvider {

    private final AeroGearLogger logger = AeroGearLogger.getInstance(SenderConfigurationProvider.class);

    @Produces @ApplicationScoped @SenderType(VariantType.ANDROID)
    public SenderConfiguration produceAndroidConfiguration() {
        return loadConfigurationFor(VariantType.ANDROID, new SenderConfiguration(10, 1000));
    }

    @Produces @ApplicationScoped @SenderType(VariantType.ADM)
    public SenderConfiguration produceAdmConfiguration() {
        return loadConfigurationFor(VariantType.ADM, new SenderConfiguration(10, 1000));
    }

    @Produces @ApplicationScoped @SenderType(VariantType.IOS)
    public SenderConfiguration produceIosConfiguration() {
        return loadConfigurationFor(VariantType.IOS, new SenderConfiguration(3, 10000));
    }

    @Produces @ApplicationScoped @SenderType(VariantType.SIMPLE_PUSH)
    public SenderConfiguration produceSimplePushConfiguration() {
        return loadConfigurationFor(VariantType.SIMPLE_PUSH, new SenderConfiguration(10, 1000));
    }

    @Produces @ApplicationScoped @SenderType(VariantType.WINDOWS_MPNS)
    public SenderConfiguration produceWindowsMpnsConfiguration() {
        return loadConfigurationFor(VariantType.WINDOWS_MPNS, new SenderConfiguration(10, 1000));
    }

    @Produces @ApplicationScoped @SenderType(VariantType.WINDOWS_WNS)
    public SenderConfiguration produceWindowsWnsConfiguration() {
        return loadConfigurationFor(VariantType.WINDOWS_WNS, new SenderConfiguration(10, 1000));
    }

    private SenderConfiguration loadConfigurationFor(VariantType type, SenderConfiguration defaultConfiguration) {
        return validateAndSanitizeConfiguration(type, new SenderConfiguration(
                getProperty(type, ConfigurationProperty.batchesToLoad, defaultConfiguration.batchesToLoad(), Integer.class),
                getProperty(type, ConfigurationProperty.batchSize, defaultConfiguration.batchSize(), Integer.class)
            ));
    }

    /**
     * Validates that configuration is correct with regards to push networks limitations or implementation, etc.
     */
    private SenderConfiguration validateAndSanitizeConfiguration(VariantType type, SenderConfiguration configuration) {
        switch (type) {
            case ANDROID:
                if (configuration.batchSize() > 1000) {
                    logger.warning(String
                            .format("Sender configuration -D%s=%s is invalid: at most 1000 tokens can be submitted to GCM in one batch",
                                    getSystemPropertyName(type, ConfigurationProperty.batchSize), configuration.batchSize()));
                    configuration.setBatchSize(1000);
                }
                break;
            default:
                break;
        }
        return configuration;
    }

    @SuppressWarnings("unchecked")
    private <T> T getProperty(VariantType type, ConfigurationProperty property, T defaultValue, Class<T> expectedType) {
        String systemPropertyName = getSystemPropertyName(type, property);
        if (expectedType == String.class) {
            return (T) ConfigurationUtils.tryGetProperty(systemPropertyName, (String) defaultValue);
        } else if (expectedType == Integer.class) {
            return (T) ConfigurationUtils.tryGetIntegerProperty(systemPropertyName, (Integer) defaultValue);
        } else {
            throw new IllegalStateException("Unexpected type: " + expectedType);
        }
    }

    private String getSystemPropertyName(VariantType type, ConfigurationProperty property) {
        return String.format("aerogear.%s.%s", type.getTypeName(), property.toString());
    }

    /**
     * Configuration properties are matching the properties of {@link SenderConfiguration} fields / accessors.
     * The enum members intentionally use camel-case to avoid need for name conversion.
     */
    private static enum ConfigurationProperty {
        batchesToLoad,
        batchSize;
    }
}
