/*
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 *
 * Copyright 2018 Danny Robson <danny@nerdcruft.net>
 */

#pragma once

#include "../point.hpp"


namespace cruft::geom {
    /// Represents a line that has a start and an end.
    ///
    /// It is not valid to create an unbounded segment by fixing one of the
    /// points at infinity.
    template <size_t S, typename T>
    struct segment {
        cruft::point<S,T> a; /// The start of the segment.
        cruft::point<S,T> b; /// The end of the segment.

        /// Return a copy of this object with the underlying type casted to
        /// the specified type.
        template <typename CastT>
        segment<S,CastT>
        cast (void) const {
            return {
                .a = a.template cast<CastT> (),
                .b = b.template cast<CastT> ()
            };
        }

        template <std::size_t ...Indices, typename ...ArgsT>
        segment<sizeof...(Indices),T>
        indices (ArgsT &&...args) const
        {
            return segment<sizeof...(Indices),T> {
                .a = a.template indices<Indices...> (args...),
                .b = b.template indices<Indices...> (args...),
            };
        }
    };


    /// Offset a segment by the supplied vector.
    template <std::size_t S, typename T>
    segment<S,T>
    operator+ (segment<S,T> lhs, vector<S,T> rhs)
    {
        return {
            lhs.a + rhs,
            lhs.b + rhs
        };
    }


    /// Return the squared distance from the closest point of the segment `s`
    /// to the point `p`.
    template <size_t S, typename T>
    T
    distance2 (segment<S,T> s, point<S,T> p)
    {
        const auto dir = s.b - s.a;
        const auto t1 = dot (p - s.a, dir);
        if (t1 < 0)
            return distance2 (p, s.a);

        const auto t2 = dot (dir, dir);
        if (t2 < t1)
            return distance2 (p, s.b);

        auto t = t1 / t2;
        return distance2 (p, s.a + t * dir);
    }


    /// Return the distance from a the closest point of the segment `s` to
    /// the point `p`.
    template <size_t S, typename T>
    T
    distance (segment<S,T> s, point<S,T> p)
    {
        return std::sqrt (distance2 (s, p));
    }


    /// Returns the un-normalised direction of the segment from its start to
    /// end components. ie, `a' to `b'.
    template <size_t S, typename T>
    cruft::vector<S,T>
    udirection (segment<S,T> const &val)
    {
        return val.b - val.a;
    }


    /// Returns the normalised direction of the segment.
    template <size_t S, typename T>
    cruft::vector<S,T>
    ndirection (segment<S,T> const &val)
    {
        return normalised (udirection (val));
    }


    /// An implementation of Bresenham's line algorithm.
    /// Ref: https://en.wikipedia.org/wiki/Bresenham%27s_line_algorithm
    ///
    /// This operates as a container that iterates over the integral points
    /// on the line. Use in range-for is explicitly supported.
    ///
    /// The start and end segment points will both be generated.
    class bresenham {
    public:
        bresenham (cruft::geom::segment<2,int> _src)
            : m_src (_src)
        { ; }

        class end_iterator { };
        end_iterator end (void) { return {}; }

        class begin_iterator {
        public:
            begin_iterator (cruft::geom::segment<2,int> _src)
                : m_start (_src.a)
                , m_diff  (_src.b - _src.a)
            {
                // Find the axis directions and finalise the diff
                auto const sign = select (m_diff > 0, 1, -1);
                m_diff = abs (m_diff);

                // Setup the increment for each possible major axis.
                if (m_diff.x > m_diff.y) {
                    m_dirx = { sign.x, 0 };
                    m_diry = { 0, sign.y };
                } else {
                    std::swap (m_diff.x, m_diff.y);

                    m_dirx = { 0, sign.y };
                    m_diry = { sign.x, 0 };
                }

                // Set the initial offsets
                D = 2 * m_diff.y - m_diff.x;
                x = y = 0;
            }

            begin_iterator& operator++ () &
            {
                // We've gone far enough. Drop down a line.
                if (D >= 0) {
                    y += 1;
                    D -= 2 * m_diff.x;
                }

                // Step forwards.
                D += 2 * m_diff.y;
                ++x;

                return *this;
            }

            bool operator== (end_iterator const&) const noexcept
            {
                return x >= m_diff.x + 1;
            }

            bool operator!= (end_iterator const &rhs) const noexcept
            {
                return !(*this == rhs);
            }

            cruft::point2i operator* () const noexcept
            {
                return m_start + x * m_dirx + y * m_diry;
            }

        private:
            cruft::point2i  m_start; /// The initial point of the segment.
            cruft::vector2i m_diff;  /// Magnitude of total traversal for each axis.
            cruft::vector2i m_dirx;  /// Direction of increment for the X axis.
            cruft::vector2i m_diry;  /// Direction of increment for the Y axis.

            int D; /// Stepping accumulator to detect line changes .
            int x; /// Current X parameter.
            int y; /// Current Y parameter.
        };

        begin_iterator begin (void) const { return { m_src }; }


    private:
        cruft::geom::segment<2,int> m_src;
    };


    using segment2i = segment<2,int>;
    using segment3i = segment<3,int>;

    using segment2f = segment<2,float>;
    using segment3f = segment<3,float>;


    template <size_t S, typename T>
    segment (point<S,T>, point<S,T>) -> segment<S,T>;
}