/*
 * Copyright 2014, The OpenNMS Group
 * 
 * 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.
 */
package org.opennms.newts.api;


import static com.google.common.base.Preconditions.checkNotNull;

import com.google.common.primitives.UnsignedLong;


public class Counter extends ValueType<UnsignedLong> {

    private static final long serialVersionUID = 1L;
    private static final UnsignedLong MAX32 = toUnsignedLong(0xFFFFFFFFL);
    private static final UnsignedLong MAX64 = toUnsignedLong(0xFFFFFFFFFFFFFFFFL);

    public static final String NAN_ON_COUNTER_WRAP_SYS_PROP = "org.opennms.newts.nan_on_counter_wrap";
    public static boolean NAN_ON_COUNTER_WRAP = Boolean.getBoolean(NAN_ON_COUNTER_WRAP_SYS_PROP);

    private final UnsignedLong m_value;

    public Counter(long value) {
        this(UnsignedLong.fromLongBits(value));
    }

    public Counter(UnsignedLong value) {
        m_value = checkNotNull(value, "value");
    }

    @Override
    public Counter plus(Number value) {
        return new Counter(getValue().plus(toUnsignedLong(value)));
    }

    @Override
    public Counter minus(Number value) {
        return new Counter(getValue().minus(toUnsignedLong(value)));
    }

    @Override
    public ValueType<UnsignedLong> delta(Number value) {

        UnsignedLong previous = toUnsignedLong(value);

        // If previous value is greater-than this one, we've wrapped
        if (previous.compareTo(getValue()) > 0) {
            if (NAN_ON_COUNTER_WRAP) {
                throw new ArithmeticException();
            }

            UnsignedLong count32 = getValue().plus(MAX32).plus(UnsignedLong.ONE);

            // Still smaller, this is a 64-bit counter wrap
            if (previous.compareTo(count32) > 0) {
                return new Counter(MAX64.minus(previous).plus(getValue()).plus(UnsignedLong.ONE));
            }
            // Process as 32-bit counter wrap
            else {
                return new Counter(MAX32.minus(previous).plus(getValue())).plus(UnsignedLong.ONE);
            }
        }
        // ...no counter wrap has occurred.
        else {
            return new Counter(getValue().minus(previous));
        }
    }

    @Override
    public Counter times(Number value) {
        return new Counter(getValue().times(toUnsignedLong(value)));
    }

    @Override
    public Counter divideBy(Number value) {
        return new Counter(getValue().dividedBy(toUnsignedLong(value)));
    }

    @Override
    UnsignedLong getValue() {
        return m_value;
    }

    @Override
    public MetricType getType() {
        return MetricType.COUNTER;
    }

    @Override
    public int intValue() {
        return getValue().intValue();
    }

    @Override
    public long longValue() {
        return getValue().longValue();
    }

    @Override
    public float floatValue() {
        return getValue().floatValue();
    }

    @Override
    public double doubleValue() {
        return getValue().doubleValue();
    }

    protected static UnsignedLong toUnsignedLong(Number value) {
        if (value instanceof UnsignedLong) return (UnsignedLong) value;
        return UnsignedLong.fromLongBits(value.longValue());
    }

}
