#include "log_settings.h"

#include <util/stream/str.h>

namespace NActors {
    namespace NLog {
        TSettings::TSettings(const TActorId& loggerActorId, const EComponent loggerComponent,
                             EComponent minVal, EComponent maxVal, EComponentToStringFunc func,
                             EPriority defPriority, EPriority defSamplingPriority,
                             ui32 defSamplingRate, ui64 timeThresholdMs)
            : LoggerActorId(loggerActorId)
            , LoggerComponent(loggerComponent)
            , TimeThresholdMs(timeThresholdMs)
            , AllowDrop(true)
            , ThrottleDelay(TDuration::MilliSeconds(100))
            , MinVal(0)
            , MaxVal(0)
            , Mask(0)
            , DefPriority(defPriority)
            , DefSamplingPriority(defSamplingPriority)
            , DefSamplingRate(defSamplingRate)
            , UseLocalTimestamps(false)
            , Format(PLAIN_FULL_FORMAT)
            , ShortHostName("")
            , ClusterName("")
        {
            Append(minVal, maxVal, func);
        }

        TSettings::TSettings(const TActorId& loggerActorId, const EComponent loggerComponent,
                             EPriority defPriority, EPriority defSamplingPriority,
                             ui32 defSamplingRate, ui64 timeThresholdMs)
            : LoggerActorId(loggerActorId)
            , LoggerComponent(loggerComponent)
            , TimeThresholdMs(timeThresholdMs)
            , AllowDrop(true)
            , ThrottleDelay(TDuration::MilliSeconds(100))
            , MinVal(0)
            , MaxVal(0)
            , Mask(0)
            , DefPriority(defPriority)
            , DefSamplingPriority(defSamplingPriority)
            , DefSamplingRate(defSamplingRate)
            , UseLocalTimestamps(false)
            , Format(PLAIN_FULL_FORMAT)
            , ShortHostName("")
            , ClusterName("")
        {
        }

        void TSettings::Append(EComponent minVal, EComponent maxVal, EComponentToStringFunc func) {
            Y_VERIFY(minVal >= 0, "NLog::TSettings: minVal must be non-negative");
            Y_VERIFY(maxVal > minVal, "NLog::TSettings: maxVal must be greater than minVal");

            // update bounds
            if (!MaxVal || minVal < MinVal) {
                MinVal = minVal;
            }

            if (!MaxVal || maxVal > MaxVal) {
                MaxVal = maxVal;

                // expand ComponentNames to the new bounds
                auto oldMask = Mask;
                Mask = PowerOf2Mask(MaxVal);

                TArrayHolder<TAtomic> oldComponentInfo(new TAtomic[Mask + 1]);
                ComponentInfo.Swap(oldComponentInfo);
                int startVal = oldMask ? oldMask + 1 : 0;
                for (int i = 0; i < startVal; i++) {
                    AtomicSet(ComponentInfo[i], AtomicGet(oldComponentInfo[i]));
                }

                TComponentSettings defSetting(DefPriority, DefSamplingPriority, DefSamplingRate);
                for (int i = startVal; i < Mask + 1; i++) {
                    AtomicSet(ComponentInfo[i], defSetting.Raw.Data);
                }

                ComponentNames.resize(Mask + 1);
            }

            // assign new names but validate if newly added members were not used before
            for (int i = minVal; i <= maxVal; i++) {
                Y_VERIFY(!ComponentNames[i], "component name at %d already set: %s",
                    i, ComponentNames[i].data());
                ComponentNames[i] = func(i);
            }
        }

        int TSettings::SetLevelImpl(
            const TString& name, bool isSampling,
            EPriority priority, EComponent component, TString& explanation) {
            TString titleName(name);
            titleName.to_title();

            // check priority
            if (!IsValidPriority(priority)) {
                TStringStream str;
                str << "Invalid " << name;
                explanation = str.Str();
                return 1;
            }

            if (component == InvalidComponent) {
                for (int i = 0; i < Mask + 1; i++) {
                    TComponentSettings settings = AtomicGet(ComponentInfo[i]);
                    if (isSampling) {
                        settings.Raw.X.SamplingLevel = priority;
                    } else {
                        settings.Raw.X.Level = priority;
                    }
                    AtomicSet(ComponentInfo[i], settings.Raw.Data);
                }

                TStringStream str;

                str << titleName
                    << " for all components has been changed to "
                    << PriorityToString(EPrio(priority));
                explanation = str.Str();
                return 0;
            } else {
                if (!IsValidComponent(component)) {
                    explanation = "Invalid component";
                    return 1;
                }
                TComponentSettings settings = AtomicGet(ComponentInfo[component]);
                EPriority oldPriority;
                if (isSampling) {
                    oldPriority = (EPriority)settings.Raw.X.SamplingLevel;
                    settings.Raw.X.SamplingLevel = priority;
                } else {
                    oldPriority = (EPriority)settings.Raw.X.Level;
                    settings.Raw.X.Level = priority;
                }
                AtomicSet(ComponentInfo[component], settings.Raw.Data);
                TStringStream str;
                str << titleName << " for the component " << ComponentNames[component]
                    << " has been changed from " << PriorityToString(EPrio(oldPriority))
                    << " to " << PriorityToString(EPrio(priority));
                explanation = str.Str();
                return 0;
            }
        }

        int TSettings::SetLevel(EPriority priority, EComponent component, TString& explanation) {
            return SetLevelImpl("priority", false,
                                priority, component, explanation);
        }

        int TSettings::SetSamplingLevel(EPriority priority, EComponent component, TString& explanation) {
            return SetLevelImpl("sampling priority", true,
                                priority, component, explanation);
        }

        int TSettings::SetSamplingRate(ui32 sampling, EComponent component, TString& explanation) {
            if (component == InvalidComponent) {
                for (int i = 0; i < Mask + 1; i++) {
                    TComponentSettings settings = AtomicGet(ComponentInfo[i]);
                    settings.Raw.X.SamplingRate = sampling;
                    AtomicSet(ComponentInfo[i], settings.Raw.Data);
                }
                TStringStream str;
                str << "Sampling rate for all components has been changed to " << sampling;
                explanation = str.Str();
            } else {
                if (!IsValidComponent(component)) {
                    explanation = "Invalid component";
                    return 1;
                }
                TComponentSettings settings = AtomicGet(ComponentInfo[component]);
                ui32 oldSampling = settings.Raw.X.SamplingRate;
                settings.Raw.X.SamplingRate = sampling;
                AtomicSet(ComponentInfo[component], settings.Raw.Data);
                TStringStream str;
                str << "Sampling rate for the component " << ComponentNames[component]
                    << " has been changed from " << oldSampling
                    << " to " << sampling;
                explanation = str.Str();
            }
            return 0;
        }

        int TSettings::PowerOf2Mask(int val) {
            int mask = 1;
            while ((val & mask) != val) {
                mask <<= 1;
                mask |= 1;
            }
            return mask;
        }

        bool TSettings::IsValidPriority(EPriority priority) {
            return priority == PRI_EMERG || priority == PRI_ALERT ||
                   priority == PRI_CRIT || priority == PRI_ERROR ||
                   priority == PRI_WARN || priority == PRI_NOTICE ||
                   priority == PRI_INFO || priority == PRI_DEBUG || priority == PRI_TRACE;
        }

        bool TSettings::IsValidComponent(EComponent component) {
            return (MinVal <= component) && (component <= MaxVal) && !ComponentNames[component].empty();
        }

        void TSettings::SetAllowDrop(bool val) {
            AllowDrop = val;
        }

        void TSettings::SetThrottleDelay(TDuration value) {
            ThrottleDelay = value;
        }

        void TSettings::SetUseLocalTimestamps(bool value) {
            UseLocalTimestamps = value;
        }

        EComponent TSettings::FindComponent(const TStringBuf& componentName) const {
            if (componentName.empty())
                return InvalidComponent;

            for (EComponent component = MinVal; component <= MaxVal; ++component) {
                if (ComponentNames[component] == componentName)
                    return component;
            }

            return InvalidComponent;
        }

    }

}
