﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Runtime.Serialization;

namespace DrivenRop.Experimental.ClassStyle
{
   /// <summary>
   /// This is equivalent to the Identity type in Haskell.
   /// </summary>
   /// <typeparam name="T"></typeparam>
   [DataContract]
   public class Data<T> 
      : IEnumerable<T>
   {
      [DataMember] public readonly T Value;

      public Data(T value)
      {
         Value = value;
      }

      public TResult Call<TResult>(TResult r)
      {
         return r;
      }

      public void Ignore()
      {
         // noop
      }

      public Data<TResult> Map<TResult>(Func<T, TResult> f)
      {
         return new Data<TResult>(f(Value));
      }

      public Data<TResult> MapOrCatch<TResult>(Func<T, TResult> f, Func<T, Exception, TResult> handler)
      {
         try
         {
            return f(Value);
         }
         catch (Exception e)
         {
            return handler(Value, e);
         }
      }

      public Data<TResult> MapOrThrow<TResult>(Func<T, TResult> f, Action<T, Exception> handler)
      {
         try
         {
            return f(Value);
         }
         catch (Exception e)
         {
            handler(Value, e);
            throw;
         }
      }

      public TResult MapTo<TResult>(Func<T, TResult> f)
      {
         return f(Value);
      }

      public Data<T> Tee(Action<T> f)
      {
         f(Value);
         return this;
      }

      public Data<T> TeeOrCatch(Action<T> f, Action<T, Exception> handler)
      {
         try
         {
            f(Value);
         }
         catch (Exception e)
         {
            handler(Value, e);
         }

         return this;
      }

      public Data<T> TeeOrThrow(Action<T> f, Action<T, Exception> handler)
      {
         try
         {
            f(Value);
         }
         catch (Exception e)
         {
            handler(Value, e);
            throw;
         }

         return this;
      }

      public Result<TResult> TryMap<TResult>(Func<T, TResult> f)
      {
         try
         {
            return f(Value);
         }
         catch (Exception e)
         {
            return e;
         }
      }

      public Result<T> TryTee(Action<T> f)
      {
         try
         {
            f(Value);
            return Value;
         }
         catch (Exception e)
         {
            return e;
         }
      }

      public T ValueOrDefault(T @default)
      {
         return Value;
      }

      public T ValueOrThrow(string error)
      {
         return Value;
      }

      public IEnumerator<T> GetEnumerator()
      {
         yield return Value;
      }

      IEnumerator IEnumerable.GetEnumerator()
      {
         return GetEnumerator();
      }

      public static implicit operator T(Data<T> d)
      {
         return d.Value;
      }

      public static implicit operator Data<T>(T value)
      {
         return new Data<T>(value);
      }
   }

   public static class Data
   {       
      public static Data<T> Create<T>(T value)
      {
         return new Data<T>(value);
      }
   }
}