/******************************************************************************
 * Copyright (c) 2016, Hobu Inc.
 *
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following
 * conditions are met:
 *
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in
 *       the documentation and/or other materials provided
 *       with the distribution.
 *     * Neither the name of the Martin Isenburg or Iowa Department
 *       of Natural Resources nor the names of its contributors may be
 *       used to endorse or promote products derived from this software
 *       without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
 * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
 * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
 * OF SUCH DAMAGE.
 ****************************************************************************/

#pragma once

#include <string>
#include <vector>

#include <pdal/util/Utils.hpp>

namespace pdal
{
namespace Dimension
{

enum class BaseType
{
    None = 0x000,
    Signed = 0x100,
    Unsigned = 0x200,
    Floating = 0x400
};

inline BaseType fromName(std::string name)
{
    if (name == "signed")
        return BaseType::Signed;
    else if (name == "unsigned")
        return BaseType::Unsigned;
    else if (name == "floating")
        return BaseType::Floating;
    return BaseType::None;
}

inline std::string toName(BaseType b)
{
    switch (b)
    {
    case BaseType::Signed:
        return "signed";
    case BaseType::Unsigned:
        return "unsigned";
    case BaseType::Floating:
        return "floating";
    default:
        return "";
    }
}

enum class Type
{
    None = 0,
    Unsigned8 = unsigned(BaseType::Unsigned) | 1,
    Signed8 = unsigned(BaseType::Signed) | 1,
    Unsigned16 = unsigned(BaseType::Unsigned) | 2,
    Signed16 = unsigned(BaseType::Signed) | 2,
    Unsigned32 = unsigned(BaseType::Unsigned) | 4,
    Signed32 = unsigned(BaseType::Signed) | 4,
    Unsigned64 = unsigned(BaseType::Unsigned) | 8,
    Signed64 = unsigned(BaseType::Signed) | 8,
    Float = unsigned(BaseType::Floating) | 4,
    Double = unsigned(BaseType::Floating) | 8
};

inline std::size_t size(Type t)
{
    return Utils::toNative(t) & 0xFF;
}

inline BaseType base(Type t)
{
    return BaseType(Utils::toNative(t) & 0xFF00);
}

static const int COUNT = (std::numeric_limits<uint16_t>::max)();
static const int PROPRIETARY = 0xF000;

/// Get a string reresentation of a datatype.
/// \param[in] dimtype  Dimension type.
/// \return  String representation of dimension type.
inline std::string interpretationName(Type dimtype)
{
    switch (dimtype)
    {
    case Type::None:
        return "unknown";
    case Type::Signed8:
        return "int8_t";
    case Type::Signed16:
        return "int16_t";
    case Type::Signed32:
        return "int32_t";
    case Type::Signed64:
        return "int64_t";
    case Type::Unsigned8:
        return "uint8_t";
    case Type::Unsigned16:
        return "uint16_t";
    case Type::Unsigned32:
        return "uint32_t";
    case Type::Unsigned64:
        return "uint64_t";
    case Type::Float:
        return "float";
    case Type::Double:
        return "double";
    }
    return "unknown";
}


/// Get the type corresponding to a type name.
/// \param s  Name of type.
/// \return  Corresponding type enumeration value.
inline Type type(std::string s)
{
    s = Utils::tolower(s);

    if (s == "int8_t" || s == "int8")
       return Type::Signed8;
    if (s == "int16_t" || s == "int16")
       return Type::Signed16;
    if (s == "int32_t" || s == "int32")
       return Type::Signed32;
    if (s == "int64_t" || s == "int64")
       return Type::Signed64;
    if (s == "uint8_t" || s == "uint8")
        return Type::Unsigned8;
    if (s == "uint16_t" || s == "uint16")
        return Type::Unsigned16;
    if (s == "uint32_t" || s == "uint32")
        return Type::Unsigned32;
    if (s == "uint64_t" || s == "uint64")
        return Type::Unsigned64;
    if (s == "float")
        return Type::Float;
    if (s == "double")
        return Type::Double;
    return Type::None;
}

/// Extract a dimension name of a string.  Dimension names start with an alpha
/// and continue with numbers or underscores.
/// \param s  String from which to extract dimension name.
/// \param p  Position at which to start extracting.
/// \return  Number of characters in the extracted name.
inline std::size_t extractName(const std::string& s, std::string::size_type p)
{
    if (!std::isalpha(s[p++]))
        return 0;
    auto isvalid = [](int c)
    {
        return std::isalpha(c) || std::isdigit(c) || c == '_' || c == '/';
    };
    return Utils::extract(s, p, isvalid) + 1;
}

inline std::istream& operator>>(std::istream& in, Dimension::Type& type)
{
    std::string sval;

    in >> sval;
    type = Dimension::type(sval);
    if (type == Dimension::Type::None)
        in.setstate(std::ios_base::failbit);
    return in;
}

inline std::ostream& operator<<(std::ostream& out, const Dimension::Type& type)
{
    out << Dimension::interpretationName(type);
    return out;
}

} // namespace Dimension

} // namespace pdal

