/*
 * Copyright 2010-2013 Amazon.com, Inc. or its affiliates. All Rights Reserved.
 * 
 * Licensed under the Apache License, Version 2.0 (the "License").
 * You may not use this file except in compliance with the License.
 * A copy of the License is located at
 * 
 *  http://aws.amazon.com/apache2.0
 * 
 * or in the "license" file accompanying this file. This file 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.
 */
using Amazon.Runtime;
using System;
using System.Globalization;

#if BCL || NETSTANDARD
using Amazon.Runtime.CredentialManagement;
#endif

namespace Amazon.S3
{

    /// <summary>
    /// Configuration for accessing AmazonS3 service
    /// </summary>
    public partial class AmazonS3Config : ClientConfig
    {
        private const string UseArnRegionEnvName = "AWS_S3_USE_ARN_REGION";
        private const string AccelerateEndpointSuffix = "s3-accelerate.amazonaws.com";
        private const string AccelerateDualstackEndpointSuffix = "s3-accelerate.dualstack.amazonaws.com";
        private const string AwsProfileEnvironmentVariable = "AWS_PROFILE";
        private const string DefaultProfileName = "default";
        private const string AwsS3UsEast1RegionalEndpointsEnvironmentVariable = "AWS_S3_US_EAST_1_REGIONAL_ENDPOINT";

        private bool forcePathStyle = false;
        private bool useAccelerateEndpoint = false;
        private S3UsEast1RegionalEndpointValue? s3UsEast1RegionalEndpointValue;
        private readonly RegionEndpoint legacyUSEast1GlobalRegion = RegionEndpoint.USEast1;

#if BCL || NETSTANDARD
        private static CredentialProfileStoreChain credentialProfileChain = new CredentialProfileStoreChain();
#endif
         
        /// <summary>
        /// When true, requests will always use path style addressing.
        /// </summary>
        public bool ForcePathStyle
        {
            get { return forcePathStyle; }
            set { forcePathStyle = value; }
        }

        /// <summary>
        /// Enables S3 accelerate by sending requests to the accelerate endpoint instead of the regular region endpoint. 
        /// To use this feature, the bucket name should be DNS compliant names and should not contain periods (.). 
        /// The following APIs are not supported and are sent to the regular region endpoint, even if this option is enabled:
        /// <ol> 
        /// <li>PutBucket</li>
        /// <li>ListBuckets</li>
        /// <li>DeleteBucket</li>
        /// </ol>
        /// </summary>
        /// <remarks>
        /// This option cannot be used at the same time as UseDualstackEndpoint.
        /// </remarks>
        public bool UseAccelerateEndpoint
        {
            get { return useAccelerateEndpoint; }
            set { useAccelerateEndpoint = value; }
        }

        bool? _useArnRegion;
        /// <summary>
        /// If set to true and the service package supports it the region identified in the arn for a resource
        /// will be used when making the service request.
        /// </summary>
        public bool UseArnRegion
        {
            get 
            {
                if (!this._useArnRegion.HasValue)
                {
#if BCL || NETSTANDARD
                    var profileName = Environment.GetEnvironmentVariable(AwsProfileEnvironmentVariable) ?? DefaultProfileName;
                    if (credentialProfileChain.TryGetProfile(profileName, out var profile))
                    {
                        this._useArnRegion = profile.S3UseArnRegion;
                    }

                    if (!this._useArnRegion.HasValue && !string.IsNullOrEmpty(Environment.GetEnvironmentVariable(UseArnRegionEnvName)))
                    {
                        if (bool.TryParse(Environment.GetEnvironmentVariable(UseArnRegionEnvName), out var value))
                        {
                            this._useArnRegion = value;
                        }
                    }
#endif

                    if (!this._useArnRegion.HasValue)
                    {
                        // To maintain consistency with buckets default UseArnRegion to true when client configured for us-east-1.
                        this._useArnRegion = this.RegionEndpoint == RegionEndpoint.USEast1;
                    }
                }

                return this._useArnRegion.GetValueOrDefault(); 
            }

            set { this._useArnRegion = value; }
        }

        /// <summary>
        /// USEast1RegionalEndpointValue determines wheter or not
        /// to send the us-east-1 s3 requests to the regional endpoint or to
        /// the legacy global endpoint.
        /// This flags takes precedence over the AWS_S3_US_EAST_1_REGIONAL_ENDPOINT
        /// environment variable and the credential file.
        /// </summary>
        public S3UsEast1RegionalEndpointValue? USEast1RegionalEndpointValue
        {
            get
            {
                if (s3UsEast1RegionalEndpointValue == null)
                {
                    s3UsEast1RegionalEndpointValue = CheckS3EnvironmentVariable() ?? CheckCredentialsFile() ?? S3UsEast1RegionalEndpointValue.Legacy;
                }
                return s3UsEast1RegionalEndpointValue;
            }
            set { s3UsEast1RegionalEndpointValue = value; }
        }

        /// <summary>
        /// This method contains custom initializations for the config object.
        /// </summary>
        protected override void Initialize()
        {
            this.AllowAutoRedirect = false;
#if BCL45 || NETSTANDARD
            // Set Timeout and ReadWriteTimeout for S3 to max timeout as per-request
            // timeouts are not supported.
            this.Timeout = ClientConfig.MaxTimeout;
            this.ReadWriteTimeout = ClientConfig.MaxTimeout;
#elif PCL
            // Only Timeout property is supported for WinRT and Windows Phone.
            // Set Timeout for S3 to max timeout as per-request
            // timeouts are not supported.
            this.Timeout = ClientConfig.MaxTimeout;
#endif
        }

        /// <summary>
        /// Given this client configuration, returns the service url
        /// </summary>
        /// <returns>The service url in the form of a string</returns>
        public override string DetermineServiceURL()
        {
            if (this.ServiceURL != null)
            {
                return this.ServiceURL;
            }

            var actual = this.RegionEndpoint;
            if (actual == legacyUSEast1GlobalRegion && !UseAccelerateEndpoint && !UseDualstackEndpoint
                && USEast1RegionalEndpointValue == S3UsEast1RegionalEndpointValue.Regional)
            {
                actual = RegionEndpoint.GetBySystemName("us-east-1-regional");
            }

            return GetUrl(actual, this.RegionEndpointServiceName, this.UseHttp, this.UseDualstackEndpoint);
        }

        /// <summary>
        /// If the client is configured to hit us-east-1 with the S3UsEast1RegionalEndpointValue flag not set, 
        /// this method checks whether the environment variable is present or the credential file contains a valid value
        /// </summary>
        /// <returns>A Nullable of S3UsEast1RegionalEndpointValue representing the client configuration for the regional us-east-1 endpoint</returns>
        private S3UsEast1RegionalEndpointValue? GetEndpointFlagValueForUsEast1Regional()
        {
            if (this.USEast1RegionalEndpointValue.HasValue)
            {
                return this.USEast1RegionalEndpointValue;
            }
            else
            {
                // Environment variable takes precedence over the credential file
                return CheckS3EnvironmentVariable() ?? CheckCredentialsFile();
            }
        }

        internal static string GetUrl(RegionEndpoint regionEndpoint, string regionEndpointServiceName, bool useHttp, bool useDualStack)
        {
            var endpoint = regionEndpoint.GetEndpointForService(regionEndpointServiceName, useDualStack);
            string url = new Uri(string.Format(CultureInfo.InvariantCulture, "{0}{1}", useHttp ? "http://" : "https://", endpoint.Hostname)).AbsoluteUri;
            return url;
        }
        
        /// <summary>
        /// Validate that the config object is properly configured.
        /// </summary>
        public override void Validate()
        {
            base.Validate();

            if (this.ForcePathStyle && this.UseAccelerateEndpoint)
            {
                throw new AmazonClientException(
                        @"S3 accelerate is not compatible with Path style requests. Disable Path style requests" +
                         " using AmazonS3Config.ForcePathStyle property to use S3 accelerate.");
            }

            var isExplicitAccelerateEndpoint = !string.IsNullOrEmpty(this.ServiceURL) &&
                                               (this.ServiceURL.IndexOf(AccelerateEndpointSuffix, StringComparison.OrdinalIgnoreCase) >= 0 ||
                                                this.ServiceURL.IndexOf(AccelerateDualstackEndpointSuffix, StringComparison.OrdinalIgnoreCase) >= 0);

            if (isExplicitAccelerateEndpoint)
            {

                if (this.RegionEndpoint == null && string.IsNullOrEmpty(this.AuthenticationRegion))
                {
                    throw new AmazonClientException(
                            @"Specify a region using AmazonS3Config.RegionEndpoint or AmazonS3Config.AuthenticationRegion" +
                            " to use S3 accelerate.");
                }
                else
                {
                    if (this.RegionEndpoint == null && !string.IsNullOrEmpty(this.AuthenticationRegion))
                    {
                        this.RegionEndpoint = RegionEndpoint.GetBySystemName(this.AuthenticationRegion);
                    }

                    this.UseAccelerateEndpoint = true;
                }
            }
        }

        /// <summary>
        /// Checks the AWS_S3_US_EAST_1_REGIONAL_ENDPOINT environment variable for the presence of the s3 regional flag
        /// </summary>
        /// <returns>A nullable of S3UsEast1RegionalEndpointValue</returns>
        private static S3UsEast1RegionalEndpointValue? CheckS3EnvironmentVariable()
        {
#if BCL || NETSTANDARD
            string s3RegionalFlag = Environment.GetEnvironmentVariable(AwsS3UsEast1RegionalEndpointsEnvironmentVariable);
            if (!string.IsNullOrEmpty(s3RegionalFlag))
            {
#if BCL35
                 S3UsEast1RegionalEndpointValue? s3RegionalFlagValue = null;
                 try
                 {
                     s3RegionalFlagValue = (S3UsEast1RegionalEndpointValue)Enum.Parse(typeof(S3UsEast1RegionalEndpointValue), s3RegionalFlag, true);
                 }
                 catch (Exception)
                 {
                     throw new InvalidOperationException("Invalid value for AWS_S3_US_EAST_1_REGIONAL_ENDPOINT environment variable. A string regional/legacy is expected.");
                 }
#else
                if (!Enum.TryParse<S3UsEast1RegionalEndpointValue>(s3RegionalFlag, true, out var s3RegionalFlagValue))
                {
                    throw new InvalidOperationException("Invalid value for AWS_S3_US_EAST_1_REGIONAL_ENDPOINT variable. A string regional/legacy is expected.");
                }
#endif
                return s3RegionalFlagValue;
            }
#endif
            return null;
        }

        /// <summary>
        /// Checks the credential file for the presence of the s3 regional flag
        /// </summary>
        /// <returns>A nullable of S3UsEast1RegionalEndpointValue</returns>
        private static S3UsEast1RegionalEndpointValue? CheckCredentialsFile()
        {
#if BCL || NETSTANDARD
            CredentialProfile profile;
            var profileName = Environment.GetEnvironmentVariable(AwsProfileEnvironmentVariable) ?? DefaultProfileName;
            credentialProfileChain.TryGetProfile(profileName, out profile);
            return profile?.S3RegionalEndpoint;
#else
             return null;
#endif

        }

        internal string AccelerateEndpoint
        {
            get
            {
                return this.UseDualstackEndpoint ? AccelerateDualstackEndpointSuffix : AccelerateEndpointSuffix;
            }
        }
    }
}