using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Newtonsoft.Json;
using System.Web;

namespace Common
{
    /// <summary>
    /// 记录每次（总次数设定上限，超过要求的频率即可）访问的时间，比较当前访问时间与向前指定次数那次的访问时间，如果时间短于规定的时长，则表示已经超过规定时长内访问次数了，即访问过于频繁了。
    /// 如：public static FrequencyControler DoFrequency = new FrequencyControler("act", 10, 3);//定义访问控制器允许10秒内3次请求
    /// DoFrequency.IsTooFrequently(true)//访问过于频繁！
    /// </summary>
    public class FrequencyControler
    {
        /// <summary>
        /// 访问控制器名称，用于区分其它控制器，支持多个控制器
        /// </summary>
        private string Name { get; set; }
        /// <summary>
        /// 限定时长
        /// </summary>
        private int Seconds { get; set; }
        /// <summary>
        /// 限定次数
        /// </summary>
        private int Times { get; set; }

        public readonly int MAX_TIMES = 100;

        #region 私有方法
        private string SessionNameDatelist
        {
            get { return String.Format("fc.{0}.datelist", Name); }
        }

        private string SessionNameDatepos
        {
            get { return String.Format("fc.{0}.datepos", Name); }
        }

        /// <summary>
        /// 取得用于保存每次访问时间点的数组（做队列用）
        /// </summary>
        /// <returns></returns>
        private long[] GetDateList()
        {
            if (HttpContext.Current.Session[SessionNameDatelist] == null)
            {
                HttpContext.Current.Session[SessionNameDatelist] = new long[MAX_TIMES];
            }
            return (long[])HttpContext.Current.Session[SessionNameDatelist];
        }

        /// <summary>
        /// 获取时间记录位置，相当于当前队列位置
        /// </summary>
        /// <returns></returns>
        private int GetDatepos()
        {
            if (HttpContext.Current.Session[SessionNameDatepos] == null)
            {
                HttpContext.Current.Session[SessionNameDatepos] = 0;
            }
            return (int)HttpContext.Current.Session[SessionNameDatepos];
        }

        /// <summary>
        /// 设置时间记录位置，相当于当前队列位置
        /// </summary>
        /// <param name="pos"></param>
        private void SetDatepos(int pos)
        {
            HttpContext.Current.Session[SessionNameDatepos] = pos;
        }
        #endregion

        /// <summary>
        /// 构造访问检测器，限定指定时间内最多请求次数
        /// </summary>
        /// <param name="name">名称</param>
        /// <param name="seconds">限定时间范围（秒数）</param>
        /// <param name="times">限定次数</param>
        public FrequencyControler(string name, int seconds, int times)
        {
            Name = name;
            Seconds = seconds;
            Times = times;
            if (times > MAX_TIMES) throw new Exception("限定次数设置值超出上限！");
        }

        /// <summary>
        /// 记录一次访问，在时间点数组的下一个位置（按最大长度循环覆盖存储）
        /// </summary>
        public void Record()
        {
            long[] ds = GetDateList();
            int pos = GetDatepos();
            pos = (pos + 1) % ds.Length;
            ds[pos] = DateTime.Now.Ticks;
            SetDatepos(pos);
        }

        /// <summary>
        /// 判断是否达到访问限制（并默认记录一次请求）
        /// </summary>
        /// <returns></returns>
        public bool IsTooFrequently()
        {
            return IsTooFrequently(true);
        }

        /// <summary>
        /// 判断是否达到访问限制（主要外部使用功能）
        /// </summary>
        /// <param name="isRecord">是否包含本次请求，为true时，会先记录一次请求再判断</param>
        /// <returns></returns>
        public bool IsTooFrequently(bool isRecord)
        {
            if (isRecord) Record();

            long[] ds = GetDateList();
            int pos = GetDatepos();

            // 取得之前 限定次数 位置的时间点与最后的时间点比较，
            // 如果之前的访问次数还没有达到限定次数则忽略
            int pre_pos = (pos + ds.Length - Times + 1) % ds.Length;
            long pre_ticks = ds[pre_pos];
            long now_ticks = ds[pos];
            // 如果访问的次烤都还没有达到限定次数，pre_ticks 必定为0，所以大于0时才进行比较
            if (pre_ticks > 0)
            {
                TimeSpan span = new TimeSpan(now_ticks - pre_ticks);
                if (span.TotalSeconds <= Seconds)
                {
                    return true;
                }
            }
            return false;
        }
    }
}
