#ifndef AVRpp_PGMSPACE_H
#define AVRpp_PGMSPACE_H

#include <avr/pgmspace.h>

// we need to force inlining on the functions, because many people
// compile with -Os, which does not always inline them.
#define PGMSPACE_INLINE  inline __attribute__((always_inline))

namespace __avr__
{
namespace memory
{

/** @internal */
template<typename T, size_t size>
struct __Pgm_reader
{
    PGMSPACE_INLINE
    static T read(const prog_void* p)
    {
        T retval;
        memcpy_P(reinterpret_cast<void*>(&retval), p, size);
        return retval;
    }
};

/** @internal */
template<typename T>
struct __Pgm_reader<T, 1>
{
    PGMSPACE_INLINE
    static T read(const prog_void* p)
    {
        return T(pgm_read_byte(p));
    }
};

/** @internal */
template<typename T>
struct __Pgm_reader<T, 2>
{
    PGMSPACE_INLINE
    static T read(const prog_void* p)
    {
        return *reinterpret_cast<T*>(pgm_read_word(p));
    }
};

/** @internal */
template<typename T>
struct __Pgm_reader<T, 3>
{
    PGMSPACE_INLINE
    static T read(const prog_void* p)
    {
        prog_char* pc = (prog_char*)p;
        char retval[3];
        retval[0] = pgm_read_word(pc++);
        retval[1] = pgm_read_byte(pc++);
        retval[2] = pgm_read_byte(pc);

        return reinterpret_cast<T&>(retval);
    }
};

/** @internal */
template<typename T>
struct __Pgm_reader<T, 4>
{
    PGMSPACE_INLINE
    static T read(const prog_void* p)
    {
        return T(pgm_read_dword(p));
    }
};

/** @short @~english Pointer into program space memory.
 *         @~german  Zeiger in den Programmspeicher.
 *
 *  @~english
 *   This tepmplate can mostly be used like a regular pointer, but operates on
 *   pgmspace memory. It will automatically read the data from pgmspace when
 *   dereferenced.
 *   
 *  @~german
 *   Dieses Template kann größtenteils wie ein gewöhnlicher Zeiger
 *   verwendet werden, greift aber auf den Programmspeicher zu. Bei einer
 *   Dereferenzierung werden die Daten automatisch aus dem Programmspeicher
 *   gelesen.
 *  
 *   @note 
 *     @~english You can't use the operator -> on this template.
 *     @~german  Der Operator -> kann mit diesem Template nicht eingesetzt
 *               werden.
 */
template <typename T>
class pgmspace_ptr
{
public:
    typedef int16_t ptrdiff_type;

    PGMSPACE_INLINE
    explicit pgmspace_ptr(const T* address = 0)
        : address_(address)
    {
    }

    template <typename U>
    PGMSPACE_INLINE
    explicit pgmspace_ptr(const pgmspace_ptr<U>& rhs)
        : address_((T*)rhs.address_)
    { 
    }

    PGMSPACE_INLINE
    const T operator*() const
    {
        return operator[](0);
    }

    PGMSPACE_INLINE
    const T operator[](size_t ofs) const
    {
        return __Pgm_reader<T, sizeof(T)>::read(address_ + ofs);
    }

    PGMSPACE_INLINE
    pgmspace_ptr& operator++()
    {
        *this += 1;
        return *this;
    }

    PGMSPACE_INLINE
    pgmspace_ptr operator++(int)
    {
        pgmspace_ptr ret = *this;
        ++*this;
        return ret;
    }

    PGMSPACE_INLINE
    pgmspace_ptr& operator--()
    {
        *this -= 1;
        return *this;
    }

    PGMSPACE_INLINE
    pgmspace_ptr& operator--(int)
    {
        pgmspace_ptr ret = *this;
        --*this;
        return ret;
    }

    PGMSPACE_INLINE
    pgmspace_ptr& operator+=(ptrdiff_type rhs)
    {
        address_ += rhs;
    }

    PGMSPACE_INLINE
    pgmspace_ptr& operator-=(ptrdiff_type rhs)
    {
        address_ -= rhs;
    }

    PGMSPACE_INLINE
    pgmspace_ptr operator+(ptrdiff_type rhs) const
    {
        return pgmspace_ptr(*this) += rhs;
    }

    PGMSPACE_INLINE
    pgmspace_ptr operator-(ptrdiff_type rhs) const
    {
        return pgmspace_ptr(*this) -= rhs;
    }

    PGMSPACE_INLINE
    ptrdiff_type operator-(const pgmspace_ptr& rhs) const
    {
        return address_ - rhs.address;
    }

    PGMSPACE_INLINE
    bool operator==(const pgmspace_ptr& rhs) const
    {
        return address_ == rhs.address_;
    }

    PGMSPACE_INLINE
    bool operator>(const pgmspace_ptr& rhs) const
    {
        return address_ > rhs.address_;
    }
    
    PGMSPACE_INLINE
    bool operator<(const pgmspace_ptr& rhs) const
    {
        return address_ < rhs.address_;
    }
    
    PGMSPACE_INLINE
    bool operator>=(const pgmspace_ptr& rhs) const
    {
        return address_ >= rhs.address_;
    }
    
    PGMSPACE_INLINE
    bool operator<=(const pgmspace_ptr& rhs) const
    {
        return address_ <= rhs.address_;
    }
    
    PGMSPACE_INLINE
    bool operator!=(const pgmspace_ptr& rhs) const
    {
        return address_ != rhs.address_;
    }

    
    PGMSPACE_INLINE
    bool operator!() const
    {
        return !address_;
    }

    PGMSPACE_INLINE
    operator bool() const
    {
        return address_;
    }

    PGMSPACE_INLINE
    operator pgmspace_ptr<void>() const
    {
        return pgmspace_ptr<void>(*this);
    }
    
    PGMSPACE_INLINE
    const T* value() const
    {
        return address_;
    }

    friend class pgmspace_ptr<void>;

private:
    const T* address_;
};

/** @relatesalso pgmspace_ptr
 *  @short @~english Convenience helper function to create a pgmspace_ptr.
 *         @~german  Hilfsfunktion zur Erzeugung eines pgmspace_ptr.
 *
 * @~english The following example shows what it's good for:
 * @code
 *     const int test_pgmspace PGMSPACE = 42;
 *
 *     int main()
 *     {
 *         int test = *pgmspace_ptr(&test_pgmspace); // error, template argument missing
 *         int test2 = *pgmspace(&test_pgmspace);    // ok, template argument deduced automatically
 *     }
 * @endcode
 *
 * @~german Das folgende Beispiel zeigt den Nutzen dieser Funktion:
 * @code
 *     const int test_pgmspace PGMSPACE = 42;
 *
 *     int main()
 *     {
 *         int test = *pgmspace_ptr(&test_pgmspace); // Fehler, Template-Argument fehlt
 *         int test2 = *pgmspace(&test_pgmspace);    // Ok, Template-Argument automatisch hergeleitet
 *     }
 * @endcode
 */
template<typename T>
PGMSPACE_INLINE
pgmspace_ptr<T> pgmspace(const T* ptr)
{
    return pgmspace_ptr<T>(ptr);
}

/** @relatesalso pgmspace_ptr
 *  @short
 *     @~english A memcpy overload that copies from program space to RAM
 *     @~german  Eine memcpy-Überladung, die aus dem Programmspeicher ins RAM
 *               kopiert
 */
PGMSPACE_INLINE
void* memcpy(void* target, pgmspace_ptr<void> src, size_t size)
{
    return ::memcpy_P(target, src.value(), size);
}

/** @relatesalso pgmspace_ptr
 *  @short
 *     @~english A strcasecmp overload that copmares a program space string
 *               with one in RAM
 *     @~german  Eine strcasecmp-Überladung, die eine Zeichenkette im
 *               Programmspeicher mit einer Zeichenkette im RAM vergleicht.
 */
PGMSPACE_INLINE
int strcasecmp(const char *ram_str, pgmspace_ptr<char> pgmspace_str)
{ 
    return strcasecmp_P(ram_str, pgmspace_str.value());
}

/** @relatesalso pgmspace_ptr
 *  @short 
 *     @~english A strcat overload that appends a program space string to one
 *               in RAM
 *     @~german  Eine strcat-Überladung, die eine Zeichenkette aus dem
 *               Programmspeicher an eine im RAM anhängt.
 */
PGMSPACE_INLINE
char* strcat(char* target, pgmspace_ptr<char> src)
{
    return strcat_P(target, src.value());
}

/** @relatesalso pgmspace_ptr
 *  @short
 *     @~english A strcmp that compares a program space string with one in RAM
 *     @~german  Eine strcmp-Überladung, die eine Zeichenkette im
 *               Programmspeicher mit einer im RAM vergleicht
 */
PGMSPACE_INLINE
int strcmp(const char* s1, pgmspace_ptr<char> s2)
{
    return strcmp_P(s1, s2.value());
}

/** @relatesalso pgmspace_ptr
 *  @short
 *     @~english A strcmp that compares a program space string with one in RAM
 *     @~german  Eine strcmp-Überladung, die eine Zeichenkette im
 *               Programmspeicher mit einer im RAM vergleicht
 */
PGMSPACE_INLINE
int strcmp(pgmspace_ptr<char> s1, const char* s2)
{
    return -strcmp_P(s2, s1.value());
}

/** @relatesalso pgmspace_ptr */
PGMSPACE_INLINE
char* strcpy(char* tgt, pgmspace_ptr<char> src)
{
    return strcpy_P(tgt, src.value());
}

/** @relatesalso pgmspace_ptr */
PGMSPACE_INLINE
size_t strlcat(char* tgt, pgmspace_ptr<char> src, size_t size)
{
    return strlcat_P(tgt, src.value(), size);
}

/** @relatesalso pgmspace_ptr */
PGMSPACE_INLINE
size_t strlcpy(char* tgt, pgmspace_ptr<char> src, size_t size)
{
    return strlcpy_P(tgt, src.value(), size);
}

/** @relatesalso pgmspace_ptr */
PGMSPACE_INLINE
size_t strlen(pgmspace_ptr<char> s) __ATTR_CONST__;

size_t strlen(pgmspace_ptr<char> s)
{
    return strlen_P(s.value());
}

/** @relatesalso pgmspace_ptr */
PGMSPACE_INLINE
int strncasecmp(const char* s1, pgmspace_ptr<char> s2, size_t size)
{
    return strncasecmp_P(s1, s2.value(), size);
}

/** @relatesalso pgmspace_ptr */
PGMSPACE_INLINE
char* strncat(char* tgt, pgmspace_ptr<char> src, size_t size)
{
    return strncat_P(tgt, src.value(), size);
}

/** @relatesalso pgmspace_ptr */
PGMSPACE_INLINE
int strncmp(const char* tgt, pgmspace_ptr<char> src, size_t size)
{
    return strncmp_P(tgt, src.value(), size);
}

/** @relatesalso pgmspace_ptr */
PGMSPACE_INLINE
char* strncpy(char* tgt, pgmspace_ptr<char> src, size_t size)
{
    return strncpy_P(tgt, src.value(), size);
}

/** @relatesalso pgmspace_ptr */
PGMSPACE_INLINE
size_t strnlen(pgmspace_ptr<char> s, size_t size) __ATTR_CONST__;

size_t strnlen(pgmspace_ptr<char> s, size_t size)
{
    return strnlen_P(s.value(), size);
}
  
} // namespace memory
} // namespace __avr__


/* vim: set tabstop=4 shiftwidth=4 filetype=cpp: */
#endif 
