// Copyright (C) 2005  Davis E. King (davis@dlib.net), and Nils Labugt, Keita Mochizuki
// License: Boost Software License   See LICENSE.txt for the full license.

#ifndef DLIB_FONTs_
#define DLIB_FONTs_

#include <memory>
#include <string>

#include "fonts_abstract.h"
#include "../algs.h"
#include "../serialize.h"
#include "../unicode.h"
#include "../array.h"
#include "../array2d.h"
#include "../threads.h"

namespace dlib
{

// ----------------------------------------------------------------------------------------

    class letter 
    {    
        /*!
            INITIAL VALUE
                - defined by constructor

            CONVENTION
                - if (points != 0) then
                    - points == an array of count point structs
                - w == width()
                - count == num_of_points()
        !*/
    public:
        struct point 
        {
            point (){}

            point (
                signed char x_,
                signed char y_
            ) :
                x(x_),
                y(y_)
            {}

            signed char x;
            signed char y;
        };

        letter (
        ) :
            points(0),
            w(0),
            count(0)
        {}

        letter (
            unsigned short width_,
            unsigned short point_count
        ) : 
            points(new point[point_count]),
            w(width_),
            count(point_count)
        {}

        ~letter(
        )
        {
            if (points)
                delete [] points; 
        }
            
        unsigned short width (
        ) const { return w; }
        
        unsigned short num_of_points (
        ) const { return count;}

        point& operator[] (
            unsigned short i
        ) 
        { 
            DLIB_ASSERT (i < num_of_points(),
                    "\tvoid letter::operator[]()"
                    << "\n\ti:               " << i 
                    << "\n\tnum_of_points(): " << num_of_points() );
            return points[i]; 
        }

        const point& operator[] (
            unsigned short i
        ) const 
        { 
            DLIB_ASSERT (i < num_of_points(),
                    "\tvoid letter::operator[]()"
                    << "\n\ti:               " << i 
                    << "\n\tnum_of_points(): " << num_of_points() );
            return points[i]; 
        }
    
        friend void serialize (
            const letter& item, 
            std::ostream& out 
        );   

        friend void deserialize (
            letter& item, 
            std::istream& in
        );   

        void swap (
            letter& item
        )
        {
            exchange(points, item.points);
            exchange(w, item.w);
            exchange(count, item.count);
        }

    private:
        // restricted functions
        letter(letter&);        // copy constructor
        letter& operator=(letter&);    // assignment operator

        point* points;
        unsigned short w;
        unsigned short count;
    };

    inline void swap (
        letter& a,
        letter& b
    ) { a.swap(b); }

// ----------------------------------------------------------------------------------------

    class font
    {
    public:
        virtual ~font() {}

        virtual bool has_character (
            unichar ch
        )const=0;
        bool has_character(char ch) const    { return this->has_character(zero_extend_cast<unichar>(ch)); }
        bool has_character(wchar_t ch) const { return this->has_character(zero_extend_cast<unichar>(ch)); }

        const letter& operator[] (char ch)   const { return (*this)[zero_extend_cast<unichar>(ch)]; };
        const letter& operator[] (wchar_t ch)const { return (*this)[zero_extend_cast<unichar>(ch)]; };

        virtual const letter& operator[] (
            unichar ch
        )const=0;

        virtual unsigned long height (
        ) const = 0;

        virtual unsigned long ascender (
        ) const = 0;

        virtual unsigned long left_overflow (
        ) const = 0;

        virtual unsigned long right_overflow (
        ) const = 0;

    // ------------------------------------------------------------------------------------

        template <typename T, typename traits, typename alloc>
        void compute_size (
            const std::basic_string<T,traits,alloc>& str,
            unsigned long& width,
            unsigned long& height,
            typename std::basic_string<T,traits,alloc>::size_type first = 0,
            typename std::basic_string<T,traits,alloc>::size_type last = (std::basic_string<T,traits,alloc>::npos)
        ) const
        {
            typedef std::basic_string<T,traits,alloc> string;
            DLIB_ASSERT ( (last == string::npos) || (first <= last && last < str.size())  ,
                          "\tvoid font::compute_size()"
                          << "\n\tlast == string::npos: " << ((last == string::npos)?"true":"false") 
                          << "\n\tfirst: " << (unsigned long)first 
                          << "\n\tlast:  " << (unsigned long)last 
                          << "\n\tstr.size():  " << (unsigned long)str.size() );

            unsigned long line_width = 0;
            unsigned long newlines = 0;
            width = 0;
            height = 0;

            if (str.size())
            {
                if (last == string::npos)
                    last = str.size()-1;
                const font& f = *this;

                for (typename string::size_type i = first; i <= last; ++i)
                {
                    // ignore '\r' characters
                    if (str[i] == '\r')
                        continue;

                    if (str[i] == '\n')
                    {
                        ++newlines;
                        width = std::max(width,line_width);
                        line_width = 0;
                    }
                    else
                    {
                        if (is_combining_char(str[i]) == false)
                            line_width += f[str[i]].width();
                    }
                }
                width = std::max(width,line_width);

                height = (newlines+1)*f.height();
                width += f.left_overflow() + f.right_overflow();
            }
        }

    // ------------------------------------------------------------------------------------

        template <typename C, typename T, typename traits, typename alloc, typename pixel_type>
        void draw_string (
            const C& c,
            const rectangle& rect,
            const std::basic_string<T,traits,alloc>& str,
            const pixel_type& color,
            typename std::basic_string<T,traits,alloc>::size_type first = 0,
            typename std::basic_string<T,traits,alloc>::size_type last = (std::basic_string<T,traits,alloc>::npos),
            const rectangle area_ = rectangle(std::numeric_limits<long>::min(), std::numeric_limits<long>::min(),
                                            std::numeric_limits<long>::max(), std::numeric_limits<long>::max())
        ) const
        {
            typedef std::basic_string<T,traits,alloc> string;
            DLIB_ASSERT ( (last == string::npos) || (first <= last && last < str.size())  ,
                          "\tvoid font::draw_string()"
                          << "\n\tlast == string::npos: " << ((last == string::npos)?"true":"false") 
                          << "\n\tfirst: " << (unsigned long)first 
                          << "\n\tlast:  " << (unsigned long)last 
                          << "\n\tstr.size():  " << (unsigned long)str.size() );

            rectangle area = rect.intersect(c).intersect(area_);
            if (area.is_empty() || str.size() == 0)
                return;

            if (last == string::npos)
                last = str.size();

            const font& f = *this;        
            long y_offset = rect.top() + f.ascender() - 1;
            long pos = rect.left()+f.left_overflow();

            convert_to_utf32(str.begin() + first, str.begin() + last, [&](unichar ch)
            {
                // ignore the '\r' character
                if (ch == '\r')
                    return;

                // A combining character should be applied to the previous character, and we
                // therefore make one step back. If a combining comes right after a newline, 
                // then there must be some kind of error in the string, and we don't combine.
                if (is_combining_char(ch) &&
                   pos > rect.left() + static_cast<long>(f.left_overflow()))
                {
                    pos -= f[ch].width();
                }

                if (ch == '\n')
                {
                    y_offset += f.height();
                    pos = rect.left()+f.left_overflow();
                    return;
                }

                // only look at letters in the intersection area
                if (area.bottom() + static_cast<long>(f.height()) < y_offset)
                {
                    // the string is now below our rectangle so we are done
                    return;
                }
                else if (area.left() > pos - static_cast<long>(f.left_overflow()) && 
                    pos + static_cast<long>(f[ch].width() + f.right_overflow()) < area.left() )
                {
                    pos += f[ch].width();
                    return;
                }
                else if (area.right() + static_cast<long>(f.right_overflow()) < pos)
                {
                    // keep looking because there might be a '\n' in the string that
                    // will wrap us around and put us back into our rectangle.
                    return;
                }

                // at this point in the loop we know that f[str[i]] overlaps 
                // horizontally with the intersection rectangle area.

                const letter& l = f[ch];
                for (unsigned short i = 0; i < l.num_of_points(); ++i)
                {
                    const long x = l[i].x + pos;
                    const long y = l[i].y + y_offset;
                    // draw each pixel of the letter if it is inside the intersection
                    // rectangle
                    if (area.contains(x,y))
                    {
                        assign_pixel(c[y-c.top()][x-c.left()], color);
                    }
                }

                pos += l.width();
            });
        }

        template <typename C, typename T, typename traits, typename alloc>
        void draw_string (
            const C& c,
            const rectangle& rect,
            const std::basic_string<T,traits,alloc>& str
        ) const 
        { 
            draw_string(c,rect, str, 0, 0, (std::basic_string<T,traits,alloc>::npos), 
                        rectangle(std::numeric_limits<long>::min(), std::numeric_limits<long>::min(),
                                  std::numeric_limits<long>::max(), std::numeric_limits<long>::max())); 
        }

    // ------------------------------------------------------------------------------------

        template <typename T, typename traits, typename alloc>
        const rectangle compute_cursor_rect (
            const rectangle& rect,
            const std::basic_string<T,traits,alloc>& str,
            unsigned long index,
            typename std::basic_string<T,traits,alloc>::size_type first = 0,
            typename std::basic_string<T,traits,alloc>::size_type last = (std::basic_string<T,traits,alloc>::npos)
        ) const
        {
            typedef std::basic_string<T,traits,alloc> string;
            DLIB_ASSERT ( (last == string::npos) || (first <= last && last < str.size())  ,
                          "\trectangle font::compute_cursor_rect()"
                          << "\n\tlast == string::npos: " << ((last == string::npos)?"true":"false") 
                          << "\n\tfirst: " << (unsigned long)first 
                          << "\n\tlast:  " << (unsigned long)last 
                          << "\n\tindex:  " << index
                          << "\n\tstr.size():  " << (unsigned long)str.size() );

            const font& f = *this;

            if (last == string::npos)
                last = str.size()-1;

            long x = f.left_overflow();
            long y = 0;
            int count = 0;

            if (str.size() != 0)
            {
                for (typename string::size_type i = first; i <= last && i < index; ++i)
                {
                    ++count;
                    if (str[i] == '\n')
                    {
                        x = f.left_overflow();
                        y += f.height();
                        count = 0;
                    }
                    else if (is_combining_char(str[i]) == false && 
                             str[i] != '\r')
                    {
                        x += f[str[i]].width();
                    }
                }
            }

            x += rect.left();
            y += rect.top();

            // if the cursor is at the start of a line then back it up one pixel
            if (count == 0)
                --x;

            return rectangle(x,y,x,y+f.height()-1);
        }

    // ------------------------------------------------------------------------------------

        template <typename T, typename traits, typename alloc>
        unsigned long compute_cursor_pos (
            const rectangle& rect,
            const std::basic_string<T,traits,alloc>& str,
            long x,
            long y,
            typename std::basic_string<T,traits,alloc>::size_type first = 0,
            typename std::basic_string<T,traits,alloc>::size_type last = (std::basic_string<T,traits,alloc>::npos)
        ) const
        {
            typedef std::basic_string<T,traits,alloc> string;
            DLIB_ASSERT ( (last == string::npos) || (first <= last && last < str.size())  ,
                          "\tunsigned long font::compute_cursor_pos()"
                          << "\n\tlast == string::npos: " << ((last == string::npos)?"true":"false") 
                          << "\n\tfirst: " << (unsigned long)first 
                          << "\n\tlast:  " << (unsigned long)last 
                          << "\n\tx:  " << x 
                          << "\n\ty:  " << y 
                          << "\n\tstr.size():  " << (unsigned long)str.size() );
            const font& f = *this;


            if (str.size() == 0)
                return 0;
            else if (first >= str.size())
                return static_cast<unsigned long>(str.size());

            y -= rect.top();
            x -= rect.left();
            if (y < 0)
                y = 0;
            if (x < 0)
                x = 0;

            if (last == string::npos)
                last = str.size()-1;


            // first figure out what line we are on
            typename string::size_type pos = first;
            long line = 0;
            while (static_cast<unsigned long>(y) >= f.height())
            {
                ++line;
                y -= f.height();
            }

            // find the start of the given line
            for (typename string::size_type i = first; i <= last && line != 0; ++i)
            {
                if (str[i] == '\n')
                {
                    --line;
                    pos = i + 1;
                }
            }


            // now str[pos] == the first character of the start of the line
            // that contains the cursor.
            const typename string::size_type start_of_line = pos;


            long cur_x = f.left_overflow();
            // set the current cursor position to where the mouse clicked
            while (pos <= last)
            {
                if (x <= cur_x || str[pos] == '\n')
                    break;

                if (is_combining_char(str[pos]) == false &&
                    str[pos] != '\r')
                {
                    cur_x += f[str[pos]].width();
                }
                ++pos;
            }

            if (x <= cur_x)
            {
                if (pos != start_of_line)
                {
                    // we might actually be closer to the previous character 
                    // so check for that and if so then jump us back one.
                    const long width = f[str[pos-1]].width();
                    if (x < cur_x - width/2)
                        --pos;
                }
            }
            return static_cast<unsigned long>(pos);
        }

    };

// ----------------------------------------------------------------------------------------

    const std::shared_ptr<font> get_native_font ();

// ----------------------------------------------------------------------------------------

    class default_font : public font
    {
        letter* l;


        default_font(
        );
        default_font(default_font&);        // copy constructor
        default_font& operator=(default_font&);    // assignment operator   



    public:
        static const std::shared_ptr<font>& get_font (
        )
        {        
            static mutex m;
            static std::shared_ptr<font> f;
            auto_mutex M(m);
            if (f.get() == 0)
                f.reset(new default_font);

            return f;
        }

        ~default_font(
        )
        {
            delete [] l;
        }

        unsigned long height (
        ) const { return 16; }

        unsigned long ascender (
        ) const { return 12; }

        unsigned long left_overflow (
        ) const { return 1; }

        unsigned long right_overflow (
        ) const { return 2; }

        bool has_character (
            unichar ch
        )const
        {
            if (ch < 256 && (l[ch].width() != 0 || l[ch].num_of_points() != 0))
                return true;
            else
                return false;
        }

        const letter& operator[] (
            unichar ch
        ) const
        {
            if(ch < 256)
                return l[ch];
            return l[0]; // just return one of the empty characters in this case 
        }
    };


// ----------------------------------------------------------------------------------------

    class bdf_font : public font
    {
    
    public:
        bdf_font( long default_char_ = -1 );
    
        long read_bdf_file( std::istream& in, unichar max_enc, unichar min_enc = 0 );
        unsigned long height() const
        {
            return fbb.height();
        }
        unsigned long ascender() const
        {
            return std::max( 0L, 1 - fbb.top() );
        }
        unsigned long left_overflow() const
        {
            return std::max( 0L, -fbb.left() );
        }
        unsigned long right_overflow() const
        {
            return right_overflow_;
        }
        const letter& operator[] ( unichar uch ) const
        {
            if ( !has_character(uch) )
            {
                return gl[default_char];
            }
            return gl[uch];
        }

        bool has_character (
            unichar ch
        )const
        {
            if (ch < gl.size() && (gl[ch].width() != 0 || gl[ch].num_of_points() != 0))
                return true;
            else
                return false;
        }

        void adjust_metrics();
    private:

        bool bitmap_to_letter( array2d<char>& bitmap, unichar enc, unsigned long width, int x_offset, int y_offset );

        array<letter> gl;
        unichar default_char; // if (is_intialized == true), then this MUST be an actual glyph
        bool is_initialized;
        rectangle fbb;
        unsigned long right_overflow_;
    
        unsigned global_width;
        bool has_global_width;
        long specified_default_char;
    
        bdf_font( bdf_font& );      // copy constructor
        bdf_font& operator=( bdf_font& );  // assignment operator
    
    };
    
// ----------------------------------------------------------------------------------------

}

#ifdef NO_MAKEFILE
#include "fonts.cpp"
#endif

#endif // DLIB_FONTs_

