using PostSharp.Aspects;
using PostSharp.Extensibility;
using PostSharp.Reflection;
using PostSharp.Serialization;
using System;
using System.Configuration;

namespace PostSharp.Samples.Persistence
{
  [PSerializable]
  [LinesOfCodeAvoided(2)]
  [MulticastAttributeUsage(TargetMemberAttributes = MulticastAttributes.Static)]
  public sealed class AppSettingsValueAttribute : LocationInterceptionAspect
  {
    private bool isFetched;
    private string settingName;

    public AppSettingsValueAttribute()
    {
    }

    public AppSettingsValueAttribute(string settingName)
    {
      this.settingName = settingName;
    }

    public override void CompileTimeInitialize(LocationInfo targetLocation, AspectInfo aspectInfo)
    {
      if (settingName == null)
      {
        settingName = targetLocation.Name;
      }
    }

    public override bool CompileTimeValidate(LocationInfo locationInfo)
    {
      bool isReadOnly;
      switch (locationInfo.LocationKind)
      {
        case LocationKind.Field:
          isReadOnly = locationInfo.FieldInfo.IsInitOnly;
          break;

        case LocationKind.Property:
          isReadOnly = locationInfo.PropertyInfo.SetMethod == null;
          break;

        default:
          throw new Exception();
      }

      if (isReadOnly)
      {
        Message.Write(locationInfo, SeverityType.Error, "MY001",
          "Cannot apply AppSettingsValueAttribute to a read-only field or property {0}.", locationInfo);
        return false;
      }

      return true;
    }

    public override void OnGetValue(LocationInterceptionArgs args)
    {
      if (isFetched)
      {
        args.ProceedGetValue();
      }
      else
      {
        isFetched = true;

        var stringValue = ConfigurationManager.AppSettings[settingName];

        if (!string.IsNullOrEmpty(stringValue))
        {
          var value = Convert.ChangeType(stringValue, args.Location.LocationType);
          args.SetNewValue(value);
          args.Value = value;
        }
        else
        {
          args.ProceedGetValue();
        }
      }
    }

    public override void OnSetValue(LocationInterceptionArgs args)
    {
      if (isFetched)
      {
        throw new InvalidOperationException(
          "The value of this field or property should not be changed after it has been read for the first time.");
      }
 
      base.OnSetValue(args);
    }
  }
}