/*_############################################################################
  _## 
  _##  SNMP4J - UsmUserEntry.java  
  _## 
  _##  Copyright (C) 2003-2026  Frank Fock (SNMP4J.org)
  _##  
  _##  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.snmp4j.security;

import org.snmp4j.mp.SnmpConstants;
import org.snmp4j.smi.OID;
import org.snmp4j.smi.OctetString;

import java.io.Serializable;
import java.lang.ref.WeakReference;

/**
 * The {@code UsmUserEntry} class represents a user in the Local Configuration Datastore (LCD).
 * The embedded {@link UsmUser} can be localized for an engine ID or not. The localization state is
 * determined by {@link UsmUser#isLocalized()}.
 * Adding a non-localized user to the LCD does only makes sense if (1) {@link USM#isEngineDiscoveryEnabled()} is true
 * and this user should be used to access command responders without yet unknown engine ID or if (2) this user should
 * be used to receive incoming notifications from agents with (yet) unknown authoritative engine IDs.
 * If a user can be used for discoveries of type (1) and/or (2), then it can be defined by
 * {@link UsmUserEntry#UsmUserEntry(OctetString, UsmUser)}. Otherwise a constructor wit authoritative engine ID must be
 * used.
 *
 * @author Frank Fock
 * @version 3.8.0
 */
public class UsmUserEntry implements Serializable, Comparable<UsmUserEntry> {

    private static final long serialVersionUID = -3021438367015187166L;

    private OctetString engineID;
    private OctetString userName;
    private UsmUser usmUser;
    private byte[] authenticationKey;
    private byte[] privacyKey;
    private SnmpConstants.StorageTypeEnum storageType = SnmpConstants.StorageTypeEnum.nonVolatile;

    /**
     * Specifies the owner of this entry - this value is used during event processing to avoid cyclic event updates
     */
    @SuppressWarnings("NonSerializableFieldInSerializableClass")
    private volatile WeakReference<Object> ownerReference;

    /**
     * Creates a new user entry with empty engine ID and empty user.
     */
    public UsmUserEntry() {
        engineID = new OctetString();
        userName = new OctetString();
        usmUser = new UsmUser(new OctetString(), null, null, null, null);
    }

    /**
     * Creates a user with username and associated {@link UsmUser}.
     *
     * @param userName
     *         the username of the new entry.
     * @param user
     *         the {@code UsmUser} representing the security information of the user.
     */
    public UsmUserEntry(OctetString userName, UsmUser user) {
        this.userName = userName;
        this.usmUser = user;
        if (user.isLocalized()) {
            this.engineID = user.getLocalizationEngineID();
            if ((user.getAuthenticationProtocol() != null) &&
                    (user.getAuthenticationPassphrase() != null)) {
                authenticationKey = user.getAuthenticationPassphrase().getValue();
                if ((user.getPrivacyProtocol() != null) &&
                        (user.getPrivacyPassphrase() != null)) {
                    privacyKey = user.getPrivacyPassphrase().getValue();
                }
            }
        }
    }

    /**
     * Creates a user with username and associated {@link UsmUser}.
     *
     * @param userName
     *         the username of the new entry.
     * @param engineID
     *         the authoritative engine ID associated with the user.
     * @param user
     *         the {@code UsmUser} representing the security information of the user.
     */
    public UsmUserEntry(OctetString userName,
                        OctetString engineID,
                        UsmUser user) {
        this(userName, user);
        this.engineID = engineID;
    }

    /**
     * Creates a localized user entry.
     *
     * @param engineID
     *         the engine ID for which the user has been localized.
     * @param securityName
     *         the user and security name of the new entry.
     * @param authProtocol
     *         the authentication protocol ID.
     * @param authKey
     *         the authentication key.
     * @param privProtocol
     *         the privacy protocol ID.
     * @param privKey
     *         the privacy key.
     */
    public UsmUserEntry(byte[] engineID, OctetString securityName,
                        OID authProtocol, byte[] authKey,
                        OID privProtocol, byte[] privKey) {
        this.engineID = (engineID == null) ? null : new OctetString(engineID);
        this.userName = securityName;
        this.authenticationKey = authKey;
        this.privacyKey = privKey;
        this.usmUser =
                new UsmUser(userName, authProtocol,
                        ((authenticationKey != null) ?
                                new SecretOctetString(authenticationKey) : null),
                        privProtocol,
                        ((privacyKey != null) ?
                                new SecretOctetString(privacyKey) : null), this.engineID);
    }

    public OctetString getEngineID() {
        return engineID;
    }

    public void setEngineID(OctetString engineID) {
        this.engineID = engineID;
    }

    public void setUserName(OctetString userName) {
        this.userName = userName;
    }

    public OctetString getUserName() {
        return userName;
    }

    public void setUsmUser(UsmUser usmUser) {
        this.usmUser = usmUser;
    }

    public UsmUser getUsmUser() {
        return usmUser;
    }

    public void setAuthenticationKey(byte[] authenticationKey) {
        this.authenticationKey = authenticationKey;
    }

    public byte[] getAuthenticationKey() {
        return authenticationKey;
    }

    public void setPrivacyKey(byte[] privacyKey) {
        this.privacyKey = privacyKey;
    }

    public byte[] getPrivacyKey() {
        return privacyKey;
    }

    /**
     * Compares this user entry with another one by engine ID then by their user names.
     *
     * @param other
     *         a {@code UsmUserEntry} instance.
     *
     * @return a negative integer, zero, or a positive integer as this object is less than, equal to, or greater than
     * the specified object.
     */
    public int compareTo(UsmUserEntry other) {
        int result = 0;
        if ((engineID != null) && (other.engineID != null)) {
            result = engineID.compareTo(other.engineID);
        } else if (engineID != null) {
            result = 1;
        } else if (other.engineID != null) {
            result = -1;
        }
        if (result == 0) {
            result = userName.compareTo(other.userName);
            if (result == 0) {
                result = usmUser.compareTo(other.usmUser);
            }
        }
        return result;
    }

    public SnmpConstants.StorageTypeEnum getStorageType() {
        return storageType;
    }

    public void setStorageType(SnmpConstants.StorageTypeEnum storageType) {
        this.storageType = storageType;
    }

    /**
     * Gets the owner of this user entry (if set).
     * @return
     *    an object previously set by {@link #setOwnerReference(Object)} or {@code null} (default).
     * @since 3.6.3
     */
    public Object getOwnerReference() {
        return ownerReference == null ? null : ownerReference.get();
    }

    /**
     * Sets the (weak) owner reference of this user entry. If the owner is deleted, this reference disappears and
     * {@link #getOwnerReference()} returns {@code null}.
     * @param ownerReference
     *    an object identifying itself as owner of this entry (i.e. this object may ignore
     *    {@link org.snmp4j.event.UsmUserEvent}s of this object while this reference is present.
     * @since 3.6.3
     */
    public void setOwnerReference(Object ownerReference) {
        this.ownerReference = new WeakReference<>(ownerReference);
    }

    @Override
    public String toString() {
        return "UsmUserEntry[userName=" + userName + ",usmUser=" + usmUser + ",storageType=" + storageType +
                ",ownerReference="+ ownerReference+"]";
    }

}

