using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Reflection;

namespace skyscraper5.Skyscraper.Webserver.Metrics
{
    internal class MetricsResource : ISkyscraperWebserverResource
    {
        private MetricsResource()
        {
            collectors = new List<object>();
            collectors.Add(SystemMetricCollector.GetInstance());
        }

        public static MetricsResource _instance;

        public static MetricsResource GetInstance()
        {
            if (_instance == null)
                _instance = new MetricsResource();

            return _instance;
        }

        public bool CanHandle(HttpListenerRequest request)
        {
            return request.HttpMethod.Equals("GET") && request.RawUrl.Equals("/metrics");
        }

        public void HandleRequest(HttpListenerContext context)
        {
            context.Response.ContentType = "text/plain";
            StreamWriter sw = new StreamWriter(context.Response.OutputStream);
            HashSet<string> helpPrinted = new HashSet<string>();
            foreach (object collector in collectors)
            {
                ScrapeFromObject(sw, helpPrinted, collector);
            }
            sw.Flush();
            sw.Close();
        }

        private void ScrapeFromObject(StreamWriter sw, HashSet<string> helpPrinted, object collector)
        {
            Type type = collector.GetType();
            PropertyInfo[] propertyInfos = type.GetProperties();
            foreach (PropertyInfo propertyInfo in propertyInfos)
            {
                if (!IsInterestingProperty(propertyInfo))
                    continue;

                string name = BuildName(type, propertyInfo.Name);
                if (!helpPrinted.Contains(name))
                {
                    MetricHelpAttribute metricHelpAttribute = propertyInfo.GetCustomAttribute<MetricHelpAttribute>();
                    if (metricHelpAttribute != null)
                    {
                        sw.Write("# HELP {1} {0}\n", metricHelpAttribute.Text, name);
                    }

                    MetricTypeAttribute metricTypeAttribute = propertyInfo.GetCustomAttribute<MetricTypeAttribute>();
                    if (metricTypeAttribute != null)
                    {
                        sw.Write("# TYPE {1} {0}\n", metricTypeAttribute.MetricType.ToString().ToLowerInvariant(), name);
                    }
                    helpPrinted.Add(name);
                }
                sw.Write("{0} {1}\n", name, propertyInfo.GetValue(collector));
            }
        }

        private string BuildName(Type type, string propName)
        {
            return String.Format("{0}_{1}", type.FullName, propName).ToLowerInvariant().Replace('.', '_');
        }

        private bool IsInterestingProperty(PropertyInfo pi)
        {
            IEnumerable<Attribute> attributes = pi.GetCustomAttributes();
            foreach (Attribute attribute in attributes)
            {
                MetricTypeAttribute metricTypeAttribute = attribute as MetricTypeAttribute;
                return metricTypeAttribute != null;
            }

            return false;
        }

        private List<object> collectors;

        public void AddCollector(object collector)
        {
            if (collectors.Contains(collector))
                return;

            collectors.Add(collector);
        }
    }
}
