﻿// Copyright 2016 Serilog Contributors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License 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 System;
using Serilog.Events;

// ReSharper disable VirtualMemberNeverOverridden.Global

namespace Serilog.Data
{
    /// <summary>
    /// An abstract base class for visitors that walk data in the
    /// <see cref="LogEventPropertyValue"/> format. Subclasses, by
    /// overriding appropriate methods, may search for, transform,
    /// or print the value structures being visited.
    /// </summary>
    /// <remarks>
    /// Stateless, designed to accommodate allocation-free visiting of multiple
    /// values by the same visitor instance.
    /// </remarks>
    /// <typeparam name="TState">The type of a state object passed through
    /// the visiting process.</typeparam>
    /// <typeparam name="TResult">The type of the result generated by visiting
    /// a node.</typeparam>
    public abstract class LogEventPropertyValueVisitor<TState, TResult>
    {
        /// <summary>
        /// Visit the root node type. This method delegates to
        /// a concrete Visit*Value() method appropriate for the value.
        /// </summary>
        /// <param name="state">Operation state.</param>
        /// <param name="value">The value to visit.</param>
        /// <returns>The result of visiting <paramref name="value"/>.</returns>
        protected virtual TResult Visit(TState state, LogEventPropertyValue value)
        {
            if (value == null) throw new ArgumentNullException(nameof(value));

            var sv = value as ScalarValue;
            if (sv != null)
                return VisitScalarValue(state, sv);

            var seqv = value as SequenceValue;
            if (seqv != null)
                return VisitSequenceValue(state, seqv);

            var strv = value as StructureValue;
            if (strv != null)
                return VisitStructureValue(state, strv);
            
            var dictv = value as DictionaryValue;
            if (dictv != null)
                return VisitDictionaryValue(state, dictv);

            return VisitUnsupportedValue(state, value);
        }

        /// <summary>
        /// Visit a <see cref="ScalarValue"/> value.
        /// </summary>
        /// <param name="state">Operation state.</param>
        /// <param name="scalar">The value to visit.</param>
        /// <returns>The result of visiting <paramref name="scalar"/>.</returns>
        protected abstract TResult VisitScalarValue(TState state, ScalarValue scalar);

        /// <summary>
        /// Visit a <see cref="SequenceValue"/> value.
        /// </summary>
        /// <param name="state">Operation state.</param>
        /// <param name="sequence">The value to visit.</param>
        /// <returns>The result of visiting <paramref name="sequence"/>.</returns>
        protected abstract TResult VisitSequenceValue(TState state, SequenceValue sequence);

        /// <summary>
        /// Visit a <see cref="StructureValue"/> value.
        /// </summary>
        /// <param name="state">Operation state.</param>
        /// <param name="structure">The value to visit.</param>
        /// <returns>The result of visiting <paramref name="structure"/>.</returns>
        protected abstract TResult VisitStructureValue(TState state, StructureValue structure);

        /// <summary>
        /// Visit a <see cref="DictionaryValue"/> value.
        /// </summary>
        /// <param name="state">Operation state.</param>
        /// <param name="dictionary">The value to visit.</param>
        /// <returns>The result of visiting <paramref name="dictionary"/>.</returns>
        protected abstract TResult VisitDictionaryValue(TState state, DictionaryValue dictionary);

        /// <summary>
        /// Visit a value of an unsupported type.
        /// </summary>
        /// <param name="state">Operation state.</param>
        /// <param name="value">The value to visit.</param>
        /// <returns>The result of visiting <paramref name="value"/>.</returns>
        // ReSharper disable once UnusedParameter.Global
        protected virtual TResult VisitUnsupportedValue(TState state, LogEventPropertyValue value)
        {
            if (value == null) throw new ArgumentNullException(nameof(value));
            throw new NotSupportedException($"The value {value} is not of a type supported by this visitor.");
        }
    }
}
