/*
 * 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.
 */
#include "HidItem.h"
#include "HidDefs.h"
#include "StreamIoUtil.h"
#include <iostream>

namespace HidUtil {

bool HidItem::dataAsUnsigned(unsigned int *out) const  {
    if (data.size() > 4 || data.size() == 0) {
        return false;
    }

    *out = 0;
    int shift = 0;

    for (auto i : data) {
        *out |= (i << shift);
        shift += 8;
    }
    return true;
}

bool HidItem::dataAsSigned(int *out) const {
    unsigned int u;
    if (!dataAsUnsigned(&u)) {
        return false;
    }
    size_t bitSize_1 = data.size() * 8 - 1;
    unsigned int sign = u & (1 << bitSize_1);
    *out = u | ((sign == 0) ? 0 : ( ~0 << bitSize_1));
    return true;
}

std::vector<HidItem> HidItem::tokenize(const uint8_t *begin, size_t size) {
    // construct a stream
    charvectorbuf<unsigned char> buf(begin, size);
    std::istream is(&buf);
    return tokenize(is);
}

std::vector<HidItem> HidItem::tokenize(const std::vector<uint8_t> &descriptor) {
    // construct a stream
    charvectorbuf<unsigned char> buf(descriptor);
    std::istream is(&buf);
    return tokenize(is);
}

std::vector<HidItem> HidItem::tokenize(std::istream &is) {
    std::vector<HidItem> hidToken;

    // this is important to avoid skipping characters
    is.unsetf(std::ios_base::skipws);
    while (!is.eof()) {
        HidItem i;
        is >> i;
        if (i.valid) {
            hidToken.push_back(i);
        } else {
            break;
        }
    }
    return hidToken;
}

std::istream& operator>>(std::istream &is, HidUtil::HidItem &h) {
    using namespace HidUtil::HidDef::MainTag;
    using namespace HidUtil::HidDef::TagType;

    h.valid = false;
    h.offset = is.tellg();
    h.byteSize = 0;
    unsigned char first;
    is >> first;
    if (!is.eof()) {
        static const size_t lenTable[] = { 0, 1, 2, 4 };
        size_t len = lenTable[first & 0x3]; // low 2 bits are length descriptor
        h.tag = (first >> 4);
        h.type = (first & 0xC) >> 2;

        if (h.tag == LONG_ITEM && h.type == RESERVED) { // long item
            //long item
            unsigned char b = 0;
            is >> b;
            len = b;
            is >> b;
            h.tag = b;
        }

        h.data.resize(len);
        for (auto &i : h.data) {
            if (is.eof()) {
                break;
            }
            is >> i;
        }
        h.byteSize = (ssize_t) is.tellg() - h.offset;
        h.valid = !is.eof();
    }

    return is;
}

std::ostream& operator<<(std::ostream &os, const HidUtil::HidItem &h) {
    os << "offset: " << h.offset << ", size: " << h.byteSize
       << ", tag: " << h.tag << ", type: " << h.type << ", data: ";
    if (h.data.empty()) {
        os << "[empty]";
    } else {
        os << h.data.size() << " byte(s) {";
        for (auto i : h.data) {
            os << (int) i << ", ";
        }
        os << "}";
    }
    return os;
}
} // namespace HidUtil