/*
 * Copyright (C) 2017 The Android Open Source Project
 *
 * 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 com.android.server.usb.descriptors;

import android.hardware.usb.UsbConfiguration;
import android.hardware.usb.UsbDevice;
import android.util.Log;

import com.android.server.usb.descriptors.report.ReportCanvas;
import com.android.server.usb.descriptors.report.UsbStrings;

import java.util.ArrayList;

/**
 * @hide
 * A USB Device Descriptor.
 * see usb11.pdf section 9.6.1
 */
public final class UsbDeviceDescriptor extends UsbDescriptor {
    private static final String TAG = "UsbDeviceDescriptor";
    private static final boolean DEBUG = false;

    public static final int USBSPEC_1_0 = 0x0100;
    public static final int USBSPEC_1_1 = 0x0110;
    public static final int USBSPEC_2_0 = 0x0200;

    private int mSpec;          // 2:2 bcdUSB 2 BCD USB Specification Number - BCD
    private int mDevClass;      // 4:1 class code
    private int mDevSubClass;   // 5:1 subclass code
    private int mProtocol;      // 6:1 protocol
    private byte mPacketSize;   // 7:1 Maximum Packet Size for Zero Endpoint.
                                // Valid Sizes are 8, 16, 32, 64
    private int mVendorID;      // 8:2 vendor ID
    private int mProductID;     // 10:2 product ID
    private int mDeviceRelease; // 12:2 Device Release number - BCD
    private byte mMfgIndex;     // 14:1 Index of Manufacturer String Descriptor
    private byte mProductIndex; // 15:1 Index of Product String Descriptor
    private byte mSerialIndex;  // 16:1 Index of Serial Number String Descriptor
    private byte mNumConfigs;   // 17:1 Number of Possible Configurations

    private ArrayList<UsbConfigDescriptor> mConfigDescriptors =
            new ArrayList<UsbConfigDescriptor>();

    UsbDeviceDescriptor(int length, byte type) {
        super(length, type);
        mHierarchyLevel = 1;
    }

    public int getSpec() {
        return mSpec;
    }

    public int getDevClass() {
        return mDevClass;
    }

    public int getDevSubClass() {
        return mDevSubClass;
    }

    public int getProtocol() {
        return mProtocol;
    }

    public byte getPacketSize() {
        return mPacketSize;
    }

    public int getVendorID() {
        return mVendorID;
    }

    public int getProductID() {
        return mProductID;
    }

    public int getDeviceRelease() {
        return mDeviceRelease;
    }

    // mDeviceRelease is binary-coded decimal, format DD.DD
    public String getDeviceReleaseString() {
        int hundredths = mDeviceRelease & 0xF;
        int tenths = (mDeviceRelease & 0xF0) >> 4;
        int ones = (mDeviceRelease & 0xF00) >> 8;
        int tens = (mDeviceRelease & 0xF000) >> 12;
        return String.format("%d.%d%d", tens * 10 + ones, tenths, hundredths);
    }

    public byte getMfgIndex() {
        return mMfgIndex;
    }

    public String getMfgString(UsbDescriptorParser p) {
        return p.getDescriptorString(mMfgIndex);
    }

    public byte getProductIndex() {
        return mProductIndex;
    }

    public String getProductString(UsbDescriptorParser p) {
        return p.getDescriptorString(mProductIndex);
    }

    public byte getSerialIndex() {
        return mSerialIndex;
    }

    public String getSerialString(UsbDescriptorParser p) {
        return p.getDescriptorString(mSerialIndex);
    }

    public byte getNumConfigs() {
        return mNumConfigs;
    }

    void addConfigDescriptor(UsbConfigDescriptor config) {
        mConfigDescriptors.add(config);
    }

    /**
     * @hide
     */
    public UsbDevice.Builder toAndroid(UsbDescriptorParser parser) {
        if (DEBUG) {
            Log.d(TAG, "toAndroid()");
        }

        String mfgName = getMfgString(parser);
        String prodName = getProductString(parser);
        if (DEBUG) {
            Log.d(TAG, "  mfgName:" + mfgName + " prodName:" + prodName);
        }

        String versionString = getDeviceReleaseString();
        String serialStr = getSerialString(parser);
        if (DEBUG) {
            Log.d(TAG, "  versionString:" + versionString + " serialStr:" + serialStr);
        }

        UsbConfiguration[] configs = new UsbConfiguration[mConfigDescriptors.size()];
        Log.d(TAG, "  " + configs.length + " configs");
        for (int index = 0; index < mConfigDescriptors.size(); index++) {
            configs[index] = mConfigDescriptors.get(index).toAndroid(parser);
        }
        UsbDevice.Builder device = new UsbDevice.Builder(parser.getDeviceAddr(), mVendorID,
                mProductID, mDevClass, mDevSubClass, mProtocol, mfgName, prodName, versionString,
                configs, serialStr);

        return device;
    }

    @Override
    public int parseRawDescriptors(ByteStream stream) {
        mSpec = stream.unpackUsbShort();
        mDevClass = stream.getUnsignedByte();
        mDevSubClass = stream.getUnsignedByte();
        mProtocol = stream.getUnsignedByte();
        mPacketSize = stream.getByte();
        mVendorID = stream.unpackUsbShort();
        mProductID = stream.unpackUsbShort();
        mDeviceRelease = stream.unpackUsbShort();
        mMfgIndex = stream.getByte();
        mProductIndex = stream.getByte();
        mSerialIndex = stream.getByte();
        mNumConfigs = stream.getByte();

        return mLength;
    }

    @Override
    public void report(ReportCanvas canvas) {
        super.report(canvas);

        canvas.openList();

        int spec = getSpec();
        canvas.writeListItem("Spec: " + ReportCanvas.getBCDString(spec));

        int devClass = getDevClass();
        String classStr = UsbStrings.getClassName(devClass);
        int devSubClass = getDevSubClass();
        String subClasStr = UsbStrings.getClassName(devSubClass);
        canvas.writeListItem("Class " + devClass + ": " + classStr + " Subclass"
                + devSubClass + ": " + subClasStr);
        canvas.writeListItem("Vendor ID: " + ReportCanvas.getHexString(getVendorID())
                + " Product ID: " + ReportCanvas.getHexString(getProductID())
                + " Product Release: " + ReportCanvas.getBCDString(getDeviceRelease()));

        UsbDescriptorParser parser = canvas.getParser();
        byte mfgIndex = getMfgIndex();
        String manufacturer = parser.getDescriptorString(mfgIndex);
        byte productIndex = getProductIndex();
        String product = parser.getDescriptorString(productIndex);

        canvas.writeListItem("Manufacturer " + mfgIndex + ": " + manufacturer
                + " Product " + productIndex + ": " + product);
        canvas.closeList();
    }
}
