/*
 * Licensed to The OpenNMS Group, Inc (TOG) under one or more
 * contributor license agreements.  See the LICENSE.md file
 * distributed with this work for additional information
 * regarding copyright ownership.
 *
 * TOG licenses this file to You under the GNU Affero General
 * Public License Version 3 (the "License") or (at your option)
 * any later version.  You may not use this file except in
 * compliance with the License.  You may obtain a copy of the
 * License at:
 *
 *      https://www.gnu.org/licenses/agpl-3.0.txt
 *
 * 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.netmgt.snmp.mock;

import java.math.BigInteger;
import java.net.InetAddress;
import java.nio.ByteBuffer;
import java.nio.charset.Charset;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.opennms.netmgt.snmp.InetAddrUtils;
import org.opennms.netmgt.snmp.SnmpObjId;
import org.opennms.netmgt.snmp.SnmpValue;
import org.opennms.netmgt.snmp.SnmpValueFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class MockSnmpValueFactory implements SnmpValueFactory {
	
	private static final Logger LOG = LoggerFactory.getLogger(MockSnmpValueFactory.class);
	
	final Charset m_defaultCharset;
	
	public MockSnmpValueFactory() {
		m_defaultCharset = Charset.defaultCharset();
	}

	@Override
	public SnmpValue getOctetString(final byte[] bytes) {
		return new MockSnmpValue.OctetStringSnmpValue(bytes);
	}

	@Override
	public SnmpValue getCounter32(final long val) {
		return new MockSnmpValue.Counter32SnmpValue(val);
	}

	@Override
	public SnmpValue getCounter64(final BigInteger val) {
		if (val == null) return null;
		return new MockSnmpValue.Counter64SnmpValue(val);
	}

	@Override
	public SnmpValue getGauge32(final long val) {
		return new MockSnmpValue.Gauge32SnmpValue(val);
	}

	@Override
	public SnmpValue getInt32(final int val) {
		return new MockSnmpValue.Integer32SnmpValue(val);
	}

	@Override
	public SnmpValue getIpAddress(final InetAddress val) {
		if (val == null) return null;
		return new MockSnmpValue.IpAddressSnmpValue(val);
	}

	@Override
	public SnmpValue getObjectId(final SnmpObjId objId) {
		return new MockSnmpValue.OidSnmpValue(objId);
	}

	@Override
	public SnmpValue getTimeTicks(final long val) {
		return new MockSnmpValue.TimeticksSnmpValue(val);
	}

	@Override
	public SnmpValue getValue(int type, byte[] bytes) {
		switch (type) {
			case SnmpValue.SNMP_COUNTER32:
				return new MockSnmpValue.Counter32SnmpValue(new BigInteger(bytes).longValue());
			case SnmpValue.SNMP_COUNTER64:
				return new MockSnmpValue.Counter64SnmpValue(new BigInteger(bytes));
			case SnmpValue.SNMP_END_OF_MIB:
				return MockSnmpValue.END_OF_MIB;
			case SnmpValue.SNMP_GAUGE32:
				return new MockSnmpValue.Gauge32SnmpValue(new BigInteger(bytes).longValue());
			case SnmpValue.SNMP_INT32:
				return new MockSnmpValue.Integer32SnmpValue(new BigInteger(bytes).intValue());
			case SnmpValue.SNMP_IPADDRESS:
				return new MockSnmpValue.IpAddressSnmpValue(bytes);
			case SnmpValue.SNMP_NO_SUCH_INSTANCE:
				return MockSnmpValue.NO_SUCH_INSTANCE;
			case SnmpValue.SNMP_NO_SUCH_OBJECT:
				return MockSnmpValue.NO_SUCH_OBJECT;
			case SnmpValue.SNMP_NULL:
				return MockSnmpValue.NULL_VALUE;
			case SnmpValue.SNMP_OBJECT_IDENTIFIER:
				return new MockSnmpValue.OidSnmpValue(new String(bytes));
			case SnmpValue.SNMP_OCTET_STRING:
				return new MockSnmpValue.OctetStringSnmpValue(bytes);
			case SnmpValue.SNMP_TIMETICKS:
				return new MockSnmpValue.TimeticksSnmpValue(new BigInteger(bytes).longValue());
			case SnmpValue.SNMP_OPAQUE:
				throw new IllegalArgumentException("Unable to handle opaque types in MockSnmpValue");
			default:
		        throw new IllegalArgumentException("Unknown SNMP value type: "+type);
		}
	}

	@Override
	public SnmpValue getNull() {
		return MockSnmpValue.NULL_VALUE;
	}

	@Override
	public SnmpValue getOpaque(final byte[] bs) {
		throw new IllegalArgumentException("Unable to handle opaque types in MockSnmpValue");
	}
	
	private static final Pattern HEX_CHUNK_PATTERN = Pattern.compile("(..)[ :]?");

	SnmpValue parseMibValue(String mibVal) {
	    
	    if (mibVal.startsWith("Wrong Type")) {
	        String newVal = mibVal.replaceFirst("Wrong Type \\(should be .*\\): ", "");
	        LOG.error("Bad Mib walk has value: '{}' using '{}'", mibVal, newVal);
	        mibVal = newVal;
	    }
	    
	    if (mibVal.startsWith("OID:")) {
	        return getObjectId(SnmpObjId.get(mibVal.substring("OID:".length()).trim()));
	    } else if (mibVal.startsWith("Timeticks:")) {
	        String timeticks = mibVal.substring("Timeticks:".length()).trim();
	        if (timeticks.contains("(")) {
	            timeticks = timeticks.replaceAll("^.*\\((\\d*?)\\).*$", "$1");
	        }
	        return getTimeTicks(Long.valueOf(timeticks));
	    } else if (mibVal.startsWith("STRING:")) {
	        return getOctetString(mibVal.substring("STRING:".length()).trim().getBytes());
	    } else if (mibVal.startsWith("INTEGER:")) {
	        return getInt32(Integer.valueOf(mibVal.substring("INTEGER:".length()).trim().replaceAll(" *.[Bb]ytes$", "")));
	    } else if (mibVal.startsWith("Gauge32:")) {
	        return getGauge32(Long.valueOf(mibVal.substring("Gauge32:".length()).trim()));
	    } else if (mibVal.startsWith("Counter32:")) {
	        return getCounter32(Long.valueOf(mibVal.substring("Counter32:".length()).trim()));
	    } else if (mibVal.startsWith("Counter64:")) {
	        return getCounter64(BigInteger.valueOf(Long.valueOf(mibVal.substring("Counter64:".length()).trim())));
	    } else if (mibVal.startsWith("IpAddress:")) {
	        return getIpAddress(InetAddrUtils.addr(mibVal.substring("IpAddress:".length()).trim()));
	    } else if (mibVal.startsWith("Hex-STRING:")) {
	        final String trimmed = mibVal.substring("Hex-STRING:".length()).trim();
	        final ByteBuffer bb = ByteBuffer.allocate(trimmed.length());
	        if (trimmed.matches("^.*[ :].*$")) {
	            for (final String chunk : trimmed.split("[ :]")) {
	                short s = Short.valueOf(chunk, 16);
	                bb.put((byte)(s & 0xFF));
	            }
	        } else {
	            if (trimmed.length() % 2 != 0) {
	                LOG.warn("Hex-STRING {} does not have ' ' or ':' separators, but it is an uneven number of characters.", trimmed);
	            }
	            final Matcher m = MockSnmpValueFactory.HEX_CHUNK_PATTERN.matcher(trimmed);
	            while (m.find()) {
	                short s = Short.valueOf(m.group(1), 16);
	                bb.put((byte)(s & 0xFF));
	            }
	        }
	        final byte[] parsed = new byte[bb.position()];
	        bb.flip();
	        bb.get(parsed);
	        return getOctetString(parsed);
	    } else if (mibVal.startsWith("Network Address:")) {
	        return getOctetString(mibVal.substring("Network Address:".length()).trim().getBytes());
	    } else if (mibVal.startsWith("BITS:")) {
	        return getOctetString(mibVal.substring("BITS:".length()).trim().getBytes());
	    } else if (mibVal.equals("\"\"")) {
	        return getNull();
	    }

	    throw new IllegalArgumentException("Unknown Snmp Type: "+mibVal);
	}



}
