package com.openxc.messages;



import android.os.Parcel;

import com.google.gson.annotations.SerializedName;
import com.openxc.util.Range;
import java.util.Arrays;
import java.util.HashMap;

/**
 * An abstract base class to hold common fields and logic for diagnostic
 * requests and responses.
 *
 * Diagnostic messages are keyed on the bus, message ID, mode and PID (if set).
 */
public abstract class DiagnosticMessage extends KeyedMessage {

    protected static final String ID_KEY = CanMessage.ID_KEY;
    protected static final String BUS_KEY = CanMessage.BUS_KEY;
    protected static final String MODE_KEY = "mode";
    protected static final String PID_KEY = "pid";
    protected static final String PAYLOAD_KEY = "payload";

    protected static final Range<Integer> BUS_RANGE = new Range<>(1, 2);
    protected static final Range<Integer> MODE_RANGE = new Range<>(1, 0xff);
    // Note that this is a limit of the OpenXC Vi firmware at the moment - a
    // diagnostic request can technically have a much larger payload.
    // to do this is not checked anywhere!

    @SerializedName(BUS_KEY)
    private int mBusId;

    @SerializedName(ID_KEY)
    private int mId;

    @SerializedName(MODE_KEY)
    private int mMode;

    @SerializedName(PID_KEY)
    // PID is an optional field, so it is stored as an Integer so it can be
    // nulled.
    private Integer mPid;

    @SerializedName(PAYLOAD_KEY)
    private byte[] mPayload;

    public DiagnosticMessage(int busId, int id, int mode) {
        mBusId = busId;
        mId = id;
        mMode = mode;
    }

    public DiagnosticMessage(int busId, int id, int mode, int pid) {
        this(busId, id, mode);
        mPid = pid;
    }

    public boolean hasPid() {
        return mPid != null;
    }

    public int getBusId() {
        return mBusId;
    }

    public int getId() {
        return mId;
    }

    public int getMode() {
        return mMode;
    }

    public Integer getPid() {
        return mPid;
    }

    public byte[] getPayload() {
        return mPayload;
    }

    public void setPid(int pid) {
        mPid = pid;
    }

    public boolean hasPayload() {
        return mPayload != null;
    }

    public void setPayload(byte[] payload) {
        if(payload != null) {
            mPayload = new byte[payload.length];
            System.arraycopy(payload, 0, mPayload, 0, payload.length);
        } else {
            mPayload = null;
        }
    }

    @Override
    public MessageKey getKey() {
        if(super.getKey() == null) {
            HashMap<String, Object> key = new HashMap<>();
            key.put(CanMessage.BUS_KEY, getBusId());
            key.put(CanMessage.ID_KEY, getId());
            key.put(MODE_KEY, getMode());
            key.put(PID_KEY, getPid());
            setKey(new MessageKey(key));
        }
        return super.getKey();
    }

    @Override
    public boolean equals(Object obj) {
        if(!super.equals(obj) || getClass() != obj.getClass()) {
            return false;
        }

        final DiagnosticMessage other = (DiagnosticMessage) obj;
        return mBusId == other.mBusId
                && mId == other.mId
                && mMode == other.mMode
                && ((!hasPid() && !other.hasPid())
                    || mPid.equals(other.mPid))
                && Arrays.equals(mPayload, other.mPayload);
    }

    @Override
    public void writeToParcel(Parcel out, int flags) {
        super.writeToParcel(out, flags);
        out.writeInt(getBusId());
        out.writeInt(getId());
        out.writeInt(getMode());
        out.writeValue(getPid());
        if(getPayload() != null) {
            out.writeInt(getPayload().length);
            out.writeByteArray(getPayload());
        } else {
            out.writeInt(0);
        }
    }

    @Override
    protected void readFromParcel(Parcel in) {
        super.readFromParcel(in);
        mBusId = in.readInt();
        mId = in.readInt();
        mMode = in.readInt();
        mPid = (Integer) in.readValue(Integer.class.getClassLoader());

        int payloadSize = in.readInt();
        if(payloadSize > 0) {
            mPayload = new byte[payloadSize];
            in.readByteArray(mPayload);
        }
    }

    protected DiagnosticMessage() { }
}
