/*
 * 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.
 */

#ifndef ESED_PN81A_APDU_H_
#define ESED_PN81A_APDU_H_

#include <cstddef>
#include <cstdint>
#include <iterator>
#include <vector>

namespace android {

/**
 * Helper to build an APDU command. If a data section is needed, it is left empty with dataBegin
 * and dataEnd able to return iterators to where the data should be filled in.
 *
 * The command bytes are stored sequentially in the same manner as std::vector.
 */
class CommandApdu {
public:
    CommandApdu(uint8_t cla, uint8_t ins, uint8_t p1, uint8_t p2)
            : CommandApdu(cla, ins, p1, p2, 0, 0) {}
    CommandApdu(uint8_t cla, uint8_t ins, uint8_t p1, uint8_t p2, size_t lc, size_t le);

    using iterator = std::vector<uint8_t>::iterator;
    using const_iterator = std::vector<uint8_t>::const_iterator;

    iterator begin() { return mCommand.begin(); }
    iterator end() { return mCommand.end(); }
    const_iterator begin() const { return mCommand.begin(); }
    const_iterator end() const { return mCommand.end(); }

    iterator dataBegin() { return mDataBegin; }
    iterator dataEnd() { return mDataEnd; }
    const_iterator dataBegin() const { return mDataBegin; }
    const_iterator dataEnd() const { return mDataEnd; }

    size_t size() const { return mCommand.size(); }
    size_t dataSize() const { return std::distance(mDataBegin, mDataEnd); }

    const std::vector<uint8_t>& vector() const { return mCommand; }

private:
    std::vector<uint8_t> mCommand;
    std::vector<uint8_t>::iterator mDataBegin;
    std::vector<uint8_t>::iterator mDataEnd;
};

/**
 * Helper to deconstruct a response APDU. This wraps a reference to an iterable byte container.
 */
template<typename T>
class ResponseApdu {
    static constexpr size_t STATUS_SIZE = 2;
    static constexpr uint8_t BYTES_AVAILABLE = 0x61;
    static constexpr uint8_t SW1_WARNING_NON_VOLATILE_MEMORY_UNCHANGED = 0x62;
    static constexpr uint8_t SW1_WARNING_NON_VOLATILE_MEMORY_CHANGED = 0x63;
    static constexpr uint8_t SW1_FIRST_EXECUTION_ERROR = 0x64;
    static constexpr uint8_t SW1_LAST_EXECUTION_ERROR = 0x66;
    static constexpr uint8_t SW1_FIRST_CHECKING_ERROR = 0x67;
    static constexpr uint8_t SW1_LAST_CHECKING_ERROR = 0x6f;

public:
    ResponseApdu(const T& data) : mData(data) {}

    bool ok() const {
        return static_cast<size_t>(
                std::distance(std::begin(mData), std::end(mData))) >= STATUS_SIZE;
    }

    uint8_t sw1() const { return *(std::end(mData) - 2); }
    uint8_t sw2() const { return *(std::end(mData) - 1); }
    uint16_t status() const { return (static_cast<uint16_t>(sw1()) << 8) | sw2(); }

    int8_t remainingBytes() const { return sw1() == BYTES_AVAILABLE ? sw2() : 0; }

    bool isWarning() const {
        const uint8_t sw1 = this->sw1();
        return sw1 == SW1_WARNING_NON_VOLATILE_MEMORY_UNCHANGED
            || sw1 == SW1_WARNING_NON_VOLATILE_MEMORY_CHANGED;
    }
    bool isExecutionError() const {
        const uint8_t sw1 = this->sw1();
        return sw1 >= SW1_FIRST_EXECUTION_ERROR && sw1 <= SW1_LAST_EXECUTION_ERROR;
    }
    bool isCheckingError() const {
        const uint8_t sw1 = this->sw1();
        return sw1 >= SW1_FIRST_CHECKING_ERROR && sw1 <= SW1_LAST_CHECKING_ERROR;
    }
    bool isError() const { return isExecutionError() || isCheckingError(); }

    auto dataBegin() const { return std::begin(mData); }
    auto dataEnd() const { return std::end(mData) - STATUS_SIZE; }

    size_t dataSize() const { return std::distance(dataBegin(), dataEnd()); }

private:
    const T& mData;
};

} // namespace android

#endif // ESED_PN81A_APDU_H_