/*
 * This program source code file is part of KiCad, a free EDA CAD application.
 *
 * Copyright (C) 2013-2015 CERN
 * Copyright (C) 2017-2021 KiCad Developers, see AUTHORS.txt for contributors.
 *
 * @author Tomasz Wlostowski <tomasz.wlostowski@cern.ch>
 *
 * This program is free software: you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by the
 * Free Software Foundation, either version 3 of the License, or (at your
 * option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program.  If not, see <http://www.gnu.or/licenses/>.
 */

#ifndef DIRECTION45_H
#define DIRECTION45_H

#include <geometry/seg.h>
#include <geometry/shape_line_chain.h>
#include <math/vector2d.h>

// believe or not, X11 headers have a F****ING macro called Opposite...
#undef Opposite

/**
 * Represent route directions & corner angles in a 45-degree metric.
 */
class DIRECTION_45
{
public:

    /**
     * Available directions, there are 8 of them, as on a rectilinear map (north = up) +
     * an extra undefined direction, reserved for traces that don't respect 45-degree routing
     * regime.
     *
     * @note North represents "up" to the user looking at the application, which is the negative-y
     * direction in the world coordinate space!
     */
    enum Directions : int
    {
        N           = 0,
        NE          = 1,
        E           = 2,
        SE          = 3,
        S           = 4,
        SW          = 5,
        W           = 6,
        NW          = 7,
        LAST        = 8,
        UNDEFINED   = -1
    };

    /**
     * Represent kind of angle formed by vectors heading in two DIRECTION_45s.
     */
    enum AngleType
    {
        ANG_OBTUSE      = 0x01,
        ANG_RIGHT       = 0x02,
        ANG_ACUTE       = 0x04,
        ANG_STRAIGHT    = 0x08,
        ANG_HALF_FULL   = 0x10,
        ANG_UNDEFINED   = 0x20
    };

    DIRECTION_45( Directions aDir = UNDEFINED ) : m_dir( aDir ), m_90deg( false ) {}

    /**
     * @param aVec vector in world space, whose direction will be translated into a DIRECTION_45.
     */
    DIRECTION_45( const VECTOR2I &aVec, bool a90 = false ) :
            m_90deg( a90 )
    {
        VECTOR2I vec( aVec );
        vec.y = -vec.y;
        construct_( vec );
    }

    /**
     * @param aSeg segment, whose direction will be translated into a DIRECTION_45.
     */
    DIRECTION_45( const SEG& aSeg, bool a90 = false ) :
            m_90deg( a90 )
    {
        VECTOR2I vec( aSeg.B - aSeg.A );
        vec.y = -vec.y;
        construct_( vec );
    }

    /**
     * Create a DIRECTION_45 from the endpoints of a given arc.
     *
     * @param aArc will be translated into the closest DIRECTION_45
     */
    DIRECTION_45( const SHAPE_ARC& aArc, bool a90 = false ) :
            m_90deg( a90 )
    {
        VECTOR2I vec( aArc.GetP1() - aArc.GetP0() );
        vec.y = -vec.y;
        construct_( vec );
    }

    /**
     * Format the direction in a human readable word.
     *
     * @return name of the direction
     */
    const std::string Format() const
    {
        switch( m_dir )
        {
        case N:
            return "north";

        case NE:
            return "north-east";

        case E:
            return "east";

        case SE:
            return "south-east";

        case S:
            return "south";

        case SW:
            return "south-west";

        case W:
            return "west";

        case NW:
            return "north-west";

        case UNDEFINED:
            return "undefined";

        default:
            return "<Error>";
        }
    }

    /**
     * Return a direction opposite (180 degree) to (this).
     *
     * @return opposite direction
     */
    DIRECTION_45 Opposite() const
    {
        const Directions OppositeMap[] = { S, SW, W, NW, N, NE, E, SE, UNDEFINED };
        return OppositeMap[m_dir];
    }

    /**
     * Return the type of angle between directions (this) and \a aOther.
     *
     * @param aOther direction to compare angle with
     */
    AngleType Angle( const DIRECTION_45& aOther ) const
    {
        if( m_dir == UNDEFINED || aOther.m_dir == UNDEFINED )
            return ANG_UNDEFINED;

        int d = std::abs( m_dir - aOther.m_dir );

        if( d == 1 || d == 7 )
            return ANG_OBTUSE;
        else if( d == 2 || d == 6 )
            return ANG_RIGHT;
        else if( d == 3 || d == 5 )
            return ANG_ACUTE;
        else if( d == 4 )
            return ANG_HALF_FULL;
        else
            return ANG_STRAIGHT;
    }

    /**
     * @return true, when (this) forms an obtuse angle with \a aOther.
     */
    bool IsObtuse( const DIRECTION_45& aOther ) const
    {
        return Angle( aOther ) == ANG_OBTUSE;
    }

    /**
     * Returns true if the direction is diagonal (e.g. North-West, South-East, etc).
     *
     * @return true, when diagonal.
     */
    bool IsDiagonal() const
    {
        return ( m_dir % 2 ) == 1;
    }

    bool IsDefined() const
    {
        return m_dir != UNDEFINED;
    }

    /**
     * Build a 2-segment line chain between points aP0 and aP1 and following 45-degree routing
     * regime. If aStartDiagonal is true, the trace starts with a diagonal segment.
     *
     * @param aP0 starting point
     * @param aP1 ending point
     * @param aStartDiagonal whether the first segment has to be diagonal
     * @param aFillet if true will fillet the 45-degree portion of the line chain
     * @return the trace
     */
    const SHAPE_LINE_CHAIN BuildInitialTrace( const VECTOR2I& aP0, const VECTOR2I& aP1,
                                              bool aStartDiagonal = false,
                                              bool aFillet = false ) const;

    bool operator==( const DIRECTION_45& aOther ) const
    {
        return aOther.m_dir == m_dir;
    }

    bool operator!=( const DIRECTION_45& aOther ) const
    {
        return aOther.m_dir != m_dir;
    }

    /**
     * Return the direction on the right side of this (i.e. turns right by 45 or 90 deg).
     */
    const DIRECTION_45 Right() const
    {
        DIRECTION_45 r;

        if ( m_dir != UNDEFINED )
        {
            if( m_90deg )
                r.m_dir = static_cast<Directions>( ( m_dir + 2 ) % LAST );
            else
                r.m_dir = static_cast<Directions>( ( m_dir + 1 ) % LAST );
        }

        return r;
    }

    /**
     * Return the direction on the left side of this (i.e. turns left by 45 or 90 deg).
     */
    const DIRECTION_45 Left() const
    {
        DIRECTION_45 l;

        if ( m_dir != UNDEFINED )
        {
            if( m_90deg )
                l.m_dir = static_cast<Directions>( ( m_dir + LAST - 2 ) % LAST );
            else
                l.m_dir = static_cast<Directions>( ( m_dir + LAST - 1 ) % LAST );
        }

        return l;
    }

    /**
     * @return a unit vector in world coordinate system corresponding to our direction.
     */
    const VECTOR2I ToVector() const
    {
        switch( m_dir )
        {
            case N: return VECTOR2I( 0, -1 );
            case S: return VECTOR2I( 0, 1 );
            case E: return VECTOR2I( 1, 0 );
            case W: return VECTOR2I( -1, 0 );
            case NE: return VECTOR2I( 1, -1 );
            case NW: return VECTOR2I( -1, -1 );
            case SE: return VECTOR2I( 1, 1 );
            case SW: return VECTOR2I( -1, 1 );

            default:
                return VECTOR2I( 0, 0 );
        }
    }

    int Mask() const
    {
        return 1 << ( (int) m_dir );
    }

private:

    /**
     * Calculate the direction from a vector. If the vector's angle is not a multiple of 45
     * degrees, the direction is rounded to the nearest octant.
     *
     * @param aVec our vector
     */
    void construct_( const VECTOR2I& aVec )
    {
        m_dir = UNDEFINED;

        if( aVec.x == 0 && aVec.y == 0 )
            return;

        double mag = 360.0 - ( 180.0 / M_PI * atan2( (double) aVec.y, (double) aVec.x ) ) + 90.0;

        if( mag >= 360.0 )
            mag -= 360.0;

        if( mag < 0.0 )
            mag += 360.0;

        int dir = ( mag + 22.5 ) / 45.0;

        if( dir >= LAST )
            dir -= LAST;

        if( dir < 0 )
            dir += LAST;

        m_dir = (Directions) dir;

        return;
    }

    ///< our actual direction
    Directions m_dir;

    ///< Are we routing on 45 or 90 degree increments
    bool m_90deg;
};

#endif    // DIRECTION45_H
