/*
 * RHQ Management Platform
 * Copyright (C) 2005-2008 Red Hat, Inc.
 * All rights reserved.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation version 2 of the License.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */
package org.rhq.plugins.www.snmp;

import java.util.Calendar;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.snmp4j.smi.OID;
import org.snmp4j.smi.OctetString;
import org.snmp4j.smi.SMIConstants;
import org.snmp4j.smi.Variable;
import org.snmp4j.smi.VariableBinding;

public class SNMPValue {
    private final Log log = LogFactory.getLog(this.getClass());

    public enum Type {
        LONG, STRING, LONG_CONVERTABLE
    }

    private OID oid;
    private Variable var;

    SNMPValue(VariableBinding varBinding) {
        this.oid = varBinding.getOid();
        this.var = varBinding.getVariable();
    }

    private String toHex(int val) {
        return Integer.toHexString(val & 0xff);
    }

    //from SNMPv2-TC:
    //PhysAddress ::= TEXTUAL-CONVENTION
    //DISPLAY-HINT "1x:"
    //STATUS       current
    //DESCRIPTION
    //        "Represents media- or physical-level addresses."
    //SYNTAX       OCTET STRING
    public String toPhysAddressString() {
        byte[] data = ((OctetString) this.var).getValue();

        if (data.length == 0) {
            return "0:0:0:0:0:0"; //e.g. loopback
        }

        StringBuilder buffer = new StringBuilder();

        buffer.append(toHex(data[0]));

        for (int i = 1; i < data.length; i++) {
            buffer.append(':').append(toHex(data[i]));
        }

        return buffer.toString();
    }

    public String getOID() {
        return this.oid.toString();
    }

    public Type getType() {
        switch (this.var.getSyntax()) {
        case SMIConstants.SYNTAX_INTEGER32:
        case SMIConstants.SYNTAX_COUNTER32:
        case SMIConstants.SYNTAX_COUNTER64:
        case SMIConstants.SYNTAX_TIMETICKS:
        case SMIConstants.SYNTAX_GAUGE32: {
            return Type.LONG;
        }

        case SMIConstants.SYNTAX_OCTET_STRING:

            //XXX while we are able to convert long
            //does not mean we should. treat as a string
            //for now.
            //return Type.LONG_CONVERTABLE;
        default: {
            return Type.STRING;
        }
        }
    }

    /**
     * @return The value of the variable as a long
     *
     * @throws SNMPException If the variable cannot be expressed as a long.
     */
    public long toLong() throws SNMPException {
        if (this.var.getSyntax() == SMIConstants.SYNTAX_OCTET_STRING) {
            return convertDateTimeOctetStringToLong();
        } else {
            try {
                return this.var.toLong();
            } catch (UnsupportedOperationException e) {
                String msg = "Cannot convert " + this.var.getSyntaxString() + " to long";
                throw new SNMPException(msg);
            }
        }
    }

    @Override
    public boolean equals(Object obj) {
        if (obj instanceof SNMPValue) {
            Variable var = ((SNMPValue) obj).var;
            return (var != null) && var.equals(this.var);
        }

        return false;
    }

    @Override
    public int hashCode() {
        return ((var != null) ? var.hashCode() : 0);
    }

    @Override
    public String toString() {
        return this.var.toString();
    }

    // XXX A bit of a hack - if it is an OctetString, treat
    // it like a DateAndTime (from the SNMPv2-TC MIB)
    private long convertDateTimeOctetStringToLong() throws SNMPException {
        byte[] bytes = ((OctetString) this.var).getValue();

        if (bytes.length != 8) {
            throw new SNMPException("OctetString is not in DateAndTime syntax.");
        }

        Calendar cal = Calendar.getInstance();

        int ix = 0;
        int year = (bytes[ix] > 0) ? bytes[ix] : (256 + bytes[ix]);

        year <<= 8;
        ix++;
        year += (bytes[ix] > 0) ? bytes[ix] : (256 + bytes[ix]);

        ix++;

        int month = bytes[ix++];
        int day = bytes[ix++];
        int hour = bytes[ix++];
        int minutes = bytes[ix++];
        int seconds = bytes[ix++];
        int deciseconds = bytes[ix++];

        cal.set(Calendar.YEAR, year);
        cal.set(Calendar.MONTH, (month - 1));
        cal.set(Calendar.DAY_OF_MONTH, day);
        cal.set(Calendar.HOUR_OF_DAY, hour);
        cal.set(Calendar.MINUTE, minutes);
        cal.set(Calendar.SECOND, seconds);
        cal.set(Calendar.MILLISECOND, (100 * deciseconds));
        cal.set(Calendar.ZONE_OFFSET, 0);
        cal.set(Calendar.DST_OFFSET, 0);

        if (log.isDebugEnabled()) {
            log.debug("converted to DateAndTime: millis=" + cal.getTimeInMillis() + ", date=" + cal.getTime());
        }

        return cal.getTimeInMillis();
    }
}