/*
 * Copyright 2015 Open Networking Laboratory
 *
 * 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.onosproject.pcepio.types;

import java.util.LinkedList;
import java.util.ListIterator;

import org.jboss.netty.buffer.ChannelBuffer;
import org.onosproject.pcepio.exceptions.PcepParseException;
import org.onosproject.pcepio.protocol.PcepVersion;

import com.google.common.base.MoreObjects;

/**
 * Provides Pcep Rsvp User Error Spec.
 */
public class PcepRsvpUserErrorSpec implements PcepRsvpErrorSpec {

    /*
        RSVP error spec object header.
        0             1              2             3
    +-------------+-------------+-------------+-------------+
    |       Length (bytes)      |  Class-Num  |   C-Type    |
    +-------------+-------------+-------------+-------------+
    |                                                       |
    //                  (Object contents)                   //
    |                                                       |
    +-------------+-------------+-------------+-------------+

    Ref : USER_ERROR_SPEC @ RFC5284.
    USER_ERROR_SPEC object: Class = 194, C-Type = 1

    0                   1                   2                   3
    0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
    +---------------+---------------+---------------+---------------+
    |                       Enterprise Number                       |
    +---------------+---------------+---------------+---------------+
    |    Sub Org    |  Err Desc Len |        User Error Value       |
    +---------------+---------------+---------------+---------------+
    |                                                               |
    ~                       Error Description                       ~
    |                                                               |
    +---------------+---------------+---------------+---------------+
    |                                                               |
    ~                     User-Defined Subobjects                   ~
    |                                                               |
    +---------------+---------------+---------------+---------------+
     */

    public static final byte CLASS_NUM = (byte) 0xc2;
    public static final byte CLASS_TYPE = 0x01;

    private PcepRsvpSpecObjHeader objHeader;
    private int enterpriseNum;
    private byte subOrg;
    private byte errDescLen;
    private short userErrorValue;
    private byte[] errDesc;
    private LinkedList<PcepValueType> llRsvpUserSpecSubObj;

    /**
     * Default constructor.
     *
     * @param objHeader pcep rsvp spec object header
     * @param enterpriseNum enterprise number
     * @param subOrg organization identifier value
     * @param errDescLen error description length
     * @param userErrorValue user error value
     * @param errDesc error description
     * @param llRsvpUserSpecSubObj list of subobjects
     */
    public PcepRsvpUserErrorSpec(PcepRsvpSpecObjHeader objHeader, int enterpriseNum, byte subOrg, byte errDescLen,
            short userErrorValue, byte[] errDesc, LinkedList<PcepValueType> llRsvpUserSpecSubObj) {
        this.objHeader = objHeader;
        this.enterpriseNum = enterpriseNum;
        this.subOrg = subOrg;
        this.errDescLen = errDescLen;
        this.userErrorValue = userErrorValue;
        this.errDesc = errDesc;
        this.llRsvpUserSpecSubObj = llRsvpUserSpecSubObj;
    }

    @Override
    public int write(ChannelBuffer cb) {
        int objLenIndex = objHeader.write(cb);
        cb.writeInt(enterpriseNum);
        cb.writeByte(subOrg);
        cb.writeByte(errDescLen);
        cb.writeShort(userErrorValue);
        cb.writeBytes(errDesc);

        if (llRsvpUserSpecSubObj != null) {

            ListIterator<PcepValueType> listIterator = llRsvpUserSpecSubObj.listIterator();

            while (listIterator.hasNext()) {
                PcepValueType tlv = listIterator.next();
                if (tlv == null) {
                    continue;
                }
                tlv.write(cb);
                // need to take care of padding
                int pad = tlv.getLength() % 4;
                if (0 != pad) {
                    pad = 4 - pad;
                    for (int i = 0; i < pad; ++i) {
                        cb.writeByte((byte) 0);
                    }
                }
            }
        }
        short objLen = (short) (cb.writerIndex() - objLenIndex);
        cb.setShort(objLenIndex, objLen);
        return objLen;
    }

    /**
     * Reads the channel buffer and returns object of PcepRsvpErrorSpec.
     *
     * @param cb of type channel buffer
     * @return object of PcepRsvpErrorSpec
     * @throws PcepParseException when expected object is not received
     */
    public static PcepRsvpErrorSpec read(ChannelBuffer cb) throws PcepParseException {
        PcepRsvpSpecObjHeader objHeader;
        int enterpriseNum;
        byte subOrg;
        byte errDescLen;
        short userErrorValue;
        byte[] errDesc;
        LinkedList<PcepValueType> llRsvpUserSpecSubObj = null;

        objHeader = PcepRsvpSpecObjHeader.read(cb);

        if (objHeader.getObjClassNum() != CLASS_NUM || objHeader.getObjClassType() != CLASS_TYPE) {
            throw new PcepParseException("Expected PcepRsvpUserErrorSpec object.");
        }
        enterpriseNum = cb.readInt();
        subOrg = cb.readByte();
        errDescLen = cb.readByte();
        userErrorValue = cb.readShort();
        errDesc = new byte[errDescLen];
        cb.readBytes(errDesc, 0, errDescLen);

        llRsvpUserSpecSubObj = parseErrSpecSubObj(cb);

        return new PcepRsvpUserErrorSpec(objHeader, enterpriseNum, subOrg, errDescLen, userErrorValue, errDesc,
                llRsvpUserSpecSubObj);
    }

    private static LinkedList<PcepValueType> parseErrSpecSubObj(ChannelBuffer cb) throws PcepParseException {
        LinkedList<PcepValueType> llRsvpUserSpecSubObj = new LinkedList<>();
        while (0 < cb.readableBytes()) {
            PcepValueType tlv = null;
            short hType = cb.readShort();
            int iValue = 0;
            //short hLength = cb.readShort();
            switch (hType) {
            case AutonomousSystemTlv.TYPE:
                iValue = cb.readInt();
                tlv = new AutonomousSystemTlv(iValue);
                break;
            default:
                throw new PcepParseException("Unsupported Sub TLV type :" + hType);
            }
            llRsvpUserSpecSubObj.add(tlv);
        }
        return llRsvpUserSpecSubObj;
    }

    @Override
    public PcepVersion getVersion() {
        return PcepVersion.PCEP_1;
    }

    @Override
    public short getType() {
        return StatefulRsvpErrorSpecTlv.TYPE;
    }

    @Override
    public short getLength() {
        return objHeader.getObjLen();
    }

    @Override
    public byte getClassNum() {
        return CLASS_NUM;
    }

    @Override
    public byte getClassType() {
        return CLASS_TYPE;
    }

    @Override
    public String toString() {
        return MoreObjects.toStringHelper(getClass())
                .add("enterpriseNumber", enterpriseNum)
                .add("subOrganization", subOrg)
                .add("errDescLength", errDescLen)
                .add("userErrorValue", userErrorValue)
                .add("errDesc", errDesc)
                .add("RsvpUserSpecSubObject", llRsvpUserSpecSubObj)
                .toString();
    }
}
