/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* 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/. */

#ifndef mozilla_DottedCornerFinder_h_
#define mozilla_DottedCornerFinder_h_

#include "mozilla/gfx/2D.h"
#include "mozilla/gfx/BezierUtils.h"
#include "gfxRect.h"

namespace mozilla {

// Calculate C_i and r_i for each filled/unfilled circles in dotted corner.
// Returns circle with C_{2j} and r_{2j} where 0 < 2j < n.
//
//                            ____-----------+
//                      __----  *****     ###|
//                __----      *********  ####|
//           __---  #####    ***********#####|
//        _--     #########  *****+*****#####+ C_0
//      _-       ########### *** C_1****#####|
//     /         #####+#####  *********  ####|
//    /       .  ### C_2 ###    *****     ###|
//   |            #########       ____-------+
//   |     .        #####____-----
//  |              __----
//  |    .       /
// |           /
// |  *****   |
// | ******* |
// |*********|
// |****+****|
// | C_{n-1} |
// | ******* |
// |  *****  |
// |  #####  |
// | ####### |
// |#########|
// +----+----+
//     C_n

class DottedCornerFinder
{
  typedef mozilla::gfx::Bezier Bezier;
  typedef mozilla::gfx::Float Float;
  typedef mozilla::gfx::Point Point;
  typedef mozilla::gfx::Size Size;

public:
  struct Result
  {
    // Center point of dot and its radius.
    Point C;
    Float r;

    Result(const Point& aC, Float aR)
     : C(aC), r(aR)
    {
      MOZ_ASSERT(aR >= 0);
    }
  };

  //                        aBorderRadiusX
  //                       aCornerDim.width
  //                     |<----------------->|
  //                     |                   | v
  //                   --+-------------___---+--
  //                   ^ |         __--      | |
  //                   | |       _-          | | aR0
  //                   | |     /         aC0 +--
  //                   | |   /               | ^
  //                   | |  |                |
  //  aBorderRadiusY   | | |             __--+
  // aCornerDim.height | ||            _-
  //                   | ||           /
  //                   | |           /
  //                   | |          |
  //                   | |          |
  //                   | |         |
  //                   | |         |
  //                   v |    aCn  |
  //                   --+----+----+
  //                     |    |
  //                     |<-->|
  //                      aRn
  //
  // aCornerDim and (aBorderRadiusX, aBorderRadiusY) can be different when
  // aBorderRadiusX is smaller than aRn*2 or
  // aBorderRadiusY is smaller than aR0*2.
  //
  //                                        aCornerDim.width
  //                                      |<----------------->|
  //                                      |                   |
  //                                      | aBorderRadiusX    |
  //                                      |<--------->|       |
  //                                      |           |       |
  //                   -------------------+-------__--+-------+--
  //                   ^                ^ |     _-            | ^
  //                   |                | |   /               | |
  //                   |                | |  /                | |
  //                   | aBorderRadiusY | | |                 | | aR0
  //                   |                | ||                  | |
  //                   |                | |                   | |
  // aCornerDim.height |                v |                   | v
  //                   |                --+               aC0 +--
  //                   |                  |                   |
  //                   |                  |                   |
  //                   |                  |                   |
  //                   |                  |                   |
  //                   |                  |                   |
  //                   v                  |        aCn        |
  //                   -------------------+---------+---------+
  //                                      |         |
  //                                      |<------->|
  //                                          aRn
  DottedCornerFinder(const Bezier& aOuterBezier, const Bezier& aInnerBezier,
                     mozilla::css::Corner aCorner,
                     Float aBorderRadiusX, Float aBorderRadiusY,
                     const Point& aC0, Float aR0, const Point& aCn, Float aRn,
                     const Size& aCornerDim);

  bool HasMore(void) const;
  Result Next(void);

private:
  static const size_t MAX_LOOP = 32;

  // Bezier control points for the outer curve, the inner curve, and a curve
  // that center points of circles are on (center curve).
  //
  //               ___---+ outer curve
  //           __--      |
  //         _-          |
  //       /        __---+ center curve
  //     /      __--     |
  //    |     /          |
  //   |    /        __--+ inner curve
  //  |    |       _-
  //  |    |      /
  // |     |     /
  // |    |     |
  // |    |     |
  // |    |    |
  // |    |    |
  // |    |    |
  // +----+----+
  Bezier mOuterBezier;
  Bezier mInnerBezier;
  Bezier mCenterBezier;

  mozilla::css::Corner mCorner;

  // Sign of the normal vector used in radius calculation, flipped depends on
  // corner and start and end radii.
  Float mNormalSign;

  // Center points and raii for start and end circles, mR0 >= mRn.
  // mMaxR = max(mR0, mRn)
  //
  //                           v
  //               ___---+------
  //           __--     #|#    | mRn
  //         _-        ##|##   |
  //       /           ##+## ---
  //     /              mCn    ^
  //    |               #|#
  //   |             __--+
  //  |            _-
  //  |           /
  // |           /
  // |          |
  // |          |
  // |  #####  |
  // | ####### |
  // |#########|
  // +----+----+
  // |## mC0 ##|
  // | ####### |
  // |  #####  |
  // |    |
  // |<-->|
  //
  //  mR0
  //
  Point mC0;
  Point mCn;
  Float mR0;
  Float mRn;
  Float mMaxR;

  // Parameters for the center curve with perfect circle and the inner curve.
  // The center curve doesn't necessarily share the origin with others.
  //
  //               ___---+
  //           __--      |
  //         _-          |
  //       /        __-+ |
  //     /      __--     |
  //    |     /          |
  //   |    /        __--+--
  //  |    |       _-    | ^
  //  |    |      /      | |
  // |     |     /       | |
  // |    |     |        | |
  // |    |     |        | | mInnerHeight
  // |    |    |         | |
  // |    +    |         | |
  // |         |         | v
  // +---------+---------+
  //           |         | mInnerCurveOrigin
  //           |<------->|
  //           mInnerWidth
  //
  //               ___---+
  //           __--
  //         _-
  //       /        __-+
  //     /      __--   |
  //    |     /        |
  //   |    /        __--+
  //  |    |       _-  |
  //  |    |      /    |
  // |     |     /     |
  // |    |     |      |
  // |    |     |      |
  // |    |    |       |
  // |    +--- | ------+
  // |    |    |       | mCenterCurveOrigin
  // +    |    +       |
  //      |            |
  //      |            |
  //      |            |
  //      |            |
  //      |<---------->|
  //       mCenterCurveR
  //
  Point mCenterCurveOrigin;
  Float mCenterCurveR;
  Point mInnerCurveOrigin;
  Float mInnerWidth;
  Float mInnerHeight;

  Point mLastC;
  Float mLastR;
  Float mLastT;

  // Overlap between two circles.
  // It uses arc length on PERFECT, SINGLE_CURVE_AND_RADIUS, and SINGLE_CURVE,
  // and direct distance on OTHER.
  Float mBestOverlap;

  // If one of border-widths is 0, do not calculate overlap, and draw circles
  // until it reaches the other side or exceeds mMaxCount.
  bool mHasZeroBorderWidth;
  bool mHasMore;

  // The maximum number of filled/unfilled circles.
  size_t mMaxCount;

  enum {
    //                      radius.width
    //                 |<----------------->|
    //                 |                   |
    //               --+-------------___---+----
    //               ^ |         __--     #|#  ^
    //               | |       _-        ##|## |
    //               | |     /           ##+## | top-width
    //               | |   /             ##|## |
    //               | |  |               #|#  v
    //               | | |             __--+----
    // radius.height | ||            _-
    //               | ||           /
    //               | |           /
    //               | |          |
    //               | |          |
    //               | |  #####  |
    //               | | ####### |
    //               v |#########|
    //               --+----+----+
    //                 |#########|
    //                 | ####### |
    //                 |  #####  |
    //                 |         |
    //                 |<------->|
    //                  left-width

    // * top-width == left-width
    // * radius.width == radius.height
    // * top-width < radius.width * 2
    //
    // All circles has same radii and are on single perfect circle's arc.
    // Overlap is known.
    //
    // Split the perfect circle's arc into 2n segments, each segment's length is
    // top-width * (1 - overlap).  Place each circle's center point C_i on each
    // end of the segment, each circle's radius r_i is top-width / 2
    //
    //                       #####
    //                      #######
    // perfect             #########
    // circle's          ___---+####
    // arc     ##### __--  ## C_0 ##
    //   |    #####_-       ###|###
    //   |   ####+####       ##|##
    //   |   ##/C_i ##         |
    //   |    |######          |
    //   |   | #####           |
    //   +->|                  |
    //     |                   |
    //   ##|##                 |
    //  ###|###                |
    // ####|####               |
    // ####+-------------------+
    // ## C_n ##
    //  #######
    //   #####
    PERFECT,

    // * top-width == left-width
    // * 0.5 < radius.width / radius.height < 2.0
    // * top-width < min(radius.width, radius.height) * 2
    //
    // All circles has same radii and are on single elliptic arc.
    // Overlap is known.
    //
    // Split the elliptic arc into 2n segments, each segment's length is
    // top-width * (1 - overlap).  Place each circle's center point C_i on each
    // end of the segment, each circle's radius r_i is top-width / 2
    //
    //                            #####
    //                           #######
    //             #####        #########
    //            #######   ____----+####
    // elliptic  ######__---    ## C_0 ##
    // arc       ##__+-###       ###|###
    //   |      / # C_i #         ##|##
    //   +--> /    #####            |
    //       |                      |
    //   ###|#                      |
    //  ###|###                     |
    // ####|####                    |
    // ####+------------------------+
    // ## C_n ##
    //  #######
    //   #####
    SINGLE_CURVE_AND_RADIUS,

    // * top-width != left-width
    // * 0 < min(top-width, left-width)
    // * 0.5 < radius.width / radius.height < 2.0
    // * max(top-width, left-width) < min(radius.width, radius.height) * 2
    //
    // All circles are on single elliptic arc.
    // Overlap is unknown.
    //
    // Place each circle's center point C_i on elliptic arc, each circle's
    // radius r_i is the distance between the center point and the inner curve.
    // The arc segment's length between C_i and C_{i-1} is
    // (r_i + r_{i-1}) * (1 - overlap).
    //
    //  outer curve
    //           /
    //          /
    //         /         / center curve
    //        / ####### /
    //       /##       /#
    //      +#        /  #
    //     /#        /    #
    //    / #   C_i /     #
    //   /  #      +      #  /
    //  /   #     /  \    # / inner curve
    //      #    /     \  #/
    //       #  /   r_i  \+
    //        #/       ##/
    //        / ####### /
    //                 /
    SINGLE_CURVE,

    // Other cases.
    // Circles are not on single elliptic arc.
    // Overlap are unknown.
    //
    // Place tangent point innerTangent on the inner curve and find circle's
    // center point C_i and radius r_i where the circle is also tangent to the
    // outer curve.
    // Distance between C_i and C_{i-1} is (r_i + r_{i-1}) * (1 - overlap).
    //
    //  outer curve
    //           /
    //          /
    //         /
    //        / #######
    //       /##       ##
    //      +#           #
    //     /# \           #
    //    / #    \        #
    //   /  #      +      #  /
    //  /   #   C_i  \    # / inner curve
    //      #          \  #/
    //       #      r_i  \+
    //        ##       ##/ innerTangent
    //          ####### /
    //                 /
    OTHER
  } mType;

  size_t mI;
  size_t mCount;

  // Determine mType from parameters.
  void DetermineType(Float aBorderRadiusX, Float aBorderRadiusY);

  // Reset calculation.
  void Reset(void);

  // Find radius for the given tangent point on the inner curve such that the
  // circle is also tangent to the outer curve.
  void FindPointAndRadius(Point& C, Float& r, const Point& innerTangent,
                          const Point& normal, Float t);

  // Find next dot.
  Float FindNext(Float overlap);

  // Find mBestOverlap for parameters.
  void FindBestOverlap(Float aMinR,
                       Float aMinBorderRadius, Float aMaxBorderRadius);

  // Fill corner with dots with given overlap, and return the number of dots
  // and last two dots's overlap.
  bool GetCountAndLastOverlap(Float aOverlap,
                              size_t* aCount, Float* aActualOverlap);
};

} // namespace mozilla

#endif /* mozilla_DottedCornerFinder_h_ */