/*
 * Unit test suite for pens (and init)
 *
 * Copyright (C) 2007 Google (Evan Stade)
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
 */

#include <math.h>

#include "objbase.h"
#include "gdiplus.h"
#include "wine/test.h"

#define expect(expected,got) expect_(__LINE__, expected, got)
static inline void expect_(unsigned line, DWORD expected, DWORD got)
{
    ok_(__FILE__, line)(expected == got, "Expected %.8ld, got %.8ld\n", expected, got);
}
#define expectf(expected, got) ok(fabs(got - expected) < 0.1, "Expected %.2f, got %.2f\n", expected, got)

static void test_startup(void)
{
    GpPen *pen = NULL;
    Status status;
    struct GdiplusStartupInput gdiplusStartupInput;
    ULONG_PTR gdiplusToken;
    int gpversion;

    gdiplusStartupInput.DebugEventCallback          = NULL;
    gdiplusStartupInput.SuppressBackgroundThread    = 0;
    gdiplusStartupInput.SuppressExternalCodecs      = 0;

    for (gpversion=1; gpversion<256; gpversion++)
    {
        gdiplusStartupInput.GdiplusVersion = gpversion;
        status = GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL);
        ok(status == Ok || status == UnsupportedGdiplusVersion,
            "GdiplusStartup returned %x\n", status);
        GdiplusShutdown(gdiplusToken);
        if (status != Ok)
        {
            gpversion--;
            break;
        }
    }

    ok(gpversion > 0 && gpversion <= 2, "unexpected gdiplus version %i\n", gpversion);
    trace("gdiplus version is %i\n", gpversion);

    status = GdipCreatePen1((ARGB)0xffff00ff, 10.0f, UnitPixel, &pen);

    todo_wine
        expect(GdiplusNotInitialized, status);

    GdipDeletePen(pen);
}

static void test_constructor_destructor(void)
{
    GpStatus status;
    GpPen *pen = NULL;

    status = GdipCreatePen1((ARGB)0xffff00ff, 10.0f, UnitPixel, NULL);
    expect(InvalidParameter, status);
    ok(pen == NULL, "Expected pen to be NULL\n");

    status = GdipCreatePen1((ARGB)0xffff00ff, 10.0f, UnitPixel, &pen);
    expect(Ok, status);
    ok(pen != NULL, "Expected pen to be initialized\n");

    status = GdipDeletePen(NULL);
    expect(InvalidParameter, status);

    status = GdipDeletePen(pen);
    expect(Ok, status);
}

static void test_constructor_destructor2(void)
{
    GpStatus status;
    GpPen *pen = NULL;
    GpBrush *brush = NULL;
    GpPointF points[2];

    status = GdipCreatePen2(NULL, 10.0f, UnitPixel, &pen);
    expect(InvalidParameter, status);
    ok(pen == NULL, "Expected pen to be NULL\n");

    points[0].X = 7.0;
    points[0].Y = 11.0;
    points[1].X = 13.0;
    points[1].Y = 17.0;

    status = GdipCreateLineBrush(&points[0], &points[1], (ARGB)0xffff00ff,
                    (ARGB)0xff0000ff, WrapModeTile, (GpLineGradient **)&brush);
    expect(Ok, status);
    ok(brush != NULL, "Expected brush to be initialized\n");

    status = GdipCreatePen2(brush, 10.0f, UnitPixel, &pen);
    expect(Ok, status);
    ok(pen != NULL, "Expected pen to be initialized\n");

    status = GdipDeletePen(pen);
    expect(Ok, status);

    status = GdipDeleteBrush(brush);
    expect(Ok, status);
}

static void test_brushfill(void)
{
    GpStatus status;
    GpPen *pen;
    GpBrush *brush, *brush2;
    GpBrushType type;
    ARGB color = 0;

    /* default solid */
    GdipCreatePen1(0xdeadbeef, 4.5, UnitWorld, &pen);
    status = GdipGetPenBrushFill(pen, &brush);
    expect(Ok, status);
    GdipGetBrushType(brush, &type);
    expect(BrushTypeSolidColor, type);
    GdipGetPenColor(pen, &color);
    expect(0xdeadbeef, color);
    GdipDeleteBrush(brush);

    /* color controlled by brush */
    GdipCreateSolidFill(0xabaddeed, (GpSolidFill**)&brush);
    status = GdipSetPenBrushFill(pen, brush);
    expect(Ok, status);
    GdipGetPenColor(pen, &color);
    expect(0xabaddeed, color);
    GdipDeleteBrush(brush);
    color = 0;

    /* get returns a clone, not a reference */
    GdipGetPenBrushFill(pen, &brush);
    GdipSetSolidFillColor((GpSolidFill*)brush, 0xbeadfeed);
    GdipGetPenBrushFill(pen, &brush2);
    ok(brush != brush2, "Expected to get a clone, not a copy of the reference\n");
    GdipGetSolidFillColor((GpSolidFill*)brush2, &color);
    expect(0xabaddeed, color);
    GdipDeleteBrush(brush);
    GdipDeleteBrush(brush2);

    /* brush cannot be NULL */
    status = GdipSetPenBrushFill(pen, NULL);
    expect(InvalidParameter, status);

    GdipDeletePen(pen);
}

static void test_dasharray(void)
{
    GpPen *pen;
    GpDashStyle style;
    GpStatus status;
    REAL dashes[12];

    GdipCreatePen1(0xdeadbeef, 10.0, UnitWorld, &pen);
    dashes[0] = 10.0;
    dashes[1] = 11.0;
    dashes[2] = 12.0;
    dashes[3] = 13.0;
    dashes[4] = 14.0;
    dashes[5] = -100.0;
    dashes[6] = -100.0;
    dashes[7] = dashes[8] = dashes[9] = dashes[10] = dashes[11] = 1.0;

    /* setting the array sets the type to custom */
    GdipGetPenDashStyle(pen, &style);
    expect(DashStyleSolid, style);
    status = GdipSetPenDashArray(pen, dashes, 2);
    expect(Ok, status);
    GdipGetPenDashStyle(pen, &style);
    expect(DashStyleCustom, style);

    /* Getting the array on a non-custom pen returns invalid parameter (unless
     * you are getting 0 elements).*/
    GdipSetPenDashStyle(pen, DashStyleSolid);
    status = GdipGetPenDashArray(pen, &dashes[5], 2);
    expect(InvalidParameter, status);
    status = GdipGetPenDashArray(pen, &dashes[5], 0);
    expect(Ok, status);

    /* What does setting DashStyleCustom do to the array length? */
    GdipSetPenDashArray(pen, dashes, 2);
    GdipSetPenDashStyle(pen, DashStyleCustom);
    status = GdipGetPenDashArray(pen, &dashes[5], 2);
    expect(Ok, status);
    expectf(10.0, dashes[5]);
    expectf(11.0, dashes[6]);

    /* Set the array, then get with different sized buffers. */
    status = GdipSetPenDashArray(pen, dashes, 5);
    expect(Ok, status);
    dashes[5] = -100.0;
    dashes[6] = -100.0;
    status = GdipGetPenDashArray(pen, &dashes[5], 1);
    expect(Ok, status); /* not InsufficientBuffer! */
    expectf(10.0, dashes[5]);
    expectf(-100.0, dashes[6]);
    dashes[5] = -100.0;
    status = GdipGetPenDashArray(pen, &dashes[5], 6);
    expect(InvalidParameter, status); /* not Ok! */
    expectf(-100.0, dashes[5]);
    expectf(-100.0, dashes[6]);

    /* Some invalid array values. */
    status = GdipSetPenDashArray(pen, &dashes[7], 5);
    expect(Ok, status);
    dashes[9] = -1.0;
    status = GdipSetPenDashArray(pen, &dashes[7], 5);
    expect(InvalidParameter, status);
    dashes[9] = 0.0;
    status = GdipSetPenDashArray(pen, &dashes[7], 5);
    expect(InvalidParameter, status);

    /* Try to set with count = 0. */
    GdipSetPenDashStyle(pen, DashStyleDot);
    if (0)  /* corrupts stack on 64-bit Vista */
    {
    status = GdipSetPenDashArray(pen, dashes, 0);
    ok(status == OutOfMemory || status == InvalidParameter,
       "Expected OutOfMemory or InvalidParameter, got %.8x\n", status);
    }
    status = GdipSetPenDashArray(pen, dashes, -1);
    ok(status == OutOfMemory || status == InvalidParameter,
       "Expected OutOfMemory or InvalidParameter, got %.8x\n", status);
    GdipGetPenDashStyle(pen, &style);
    expect(DashStyleDot, style);

    GdipDeletePen(pen);
}

static void test_customcap(void)
{
    GpPen *pen;
    GpStatus status;
    GpCustomLineCap *custom;

    status = GdipCreatePen1((ARGB)0xffff00ff, 10.0f, UnitPixel, &pen);
    expect(Ok, status);

    /* NULL args */
    status = GdipGetPenCustomStartCap(NULL, NULL);
    expect(InvalidParameter, status);
    status = GdipGetPenCustomStartCap(pen, NULL);
    expect(InvalidParameter, status);
    status = GdipGetPenCustomStartCap(NULL, &custom);
    expect(InvalidParameter, status);

    status = GdipGetPenCustomEndCap(NULL, NULL);
    expect(InvalidParameter, status);
    status = GdipGetPenCustomEndCap(pen, NULL);
    expect(InvalidParameter, status);
    status = GdipGetPenCustomEndCap(NULL, &custom);
    expect(InvalidParameter, status);

    /* native crashes on pen == NULL, custom != NULL */
    status = GdipSetPenCustomStartCap(NULL, NULL);
    expect(InvalidParameter, status);
    status = GdipSetPenCustomStartCap(pen, NULL);
    expect(InvalidParameter, status);

    status = GdipSetPenCustomEndCap(NULL, NULL);
    expect(InvalidParameter, status);
    status = GdipSetPenCustomEndCap(pen, NULL);
    expect(InvalidParameter, status);

    /* get without setting previously */
    custom = (GpCustomLineCap*)0xdeadbeef;
    status = GdipGetPenCustomEndCap(pen, &custom);
    expect(Ok, status);
    ok(custom == NULL,"Expect CustomCap == NULL\n");

    custom = (GpCustomLineCap*)0xdeadbeef;
    status = GdipGetPenCustomStartCap(pen, &custom);
    expect(Ok, status);
    ok(custom == NULL,"Expect CustomCap == NULL\n");

    GdipDeletePen(pen);
}

static void test_penfilltype(void)
{
    GpPen *pen;
    GpSolidFill *solid;
    GpLineGradient *line;
    GpPointF a, b;
    GpStatus status;
    GpPenType type;

    /* NULL */
    status = GdipGetPenFillType(NULL, NULL);
    expect(InvalidParameter, status);

    status = GdipCreatePen1((ARGB)0xffff00ff, 10.0f, UnitPixel, &pen);
    expect(Ok, status);
    status = GdipGetPenFillType(pen, NULL);
    expect(InvalidParameter, status);

    /* created with GdipCreatePen1() */
    status = GdipGetPenFillType(pen, &type);
    expect(Ok, status);
    expect(PenTypeSolidColor, type);
    GdipDeletePen(pen);

    /* based on SolidBrush */
    status = GdipCreateSolidFill((ARGB)0xffff00ff, &solid);
    expect(Ok, status);
    status = GdipCreatePen2((GpBrush*)solid, 10.0f, UnitPixel, &pen);
    expect(Ok, status);
    status = GdipGetPenFillType(pen, &type);
    expect(Ok, status);
    expect(PenTypeSolidColor, type);
    GdipDeletePen(pen);
    GdipDeleteBrush((GpBrush*)solid);

    /* based on LinearGradientBrush */
    a.X = a.Y = 0.0;
    b.X = b.Y = 10.0;
    status = GdipCreateLineBrush(&a, &b, (ARGB)0xffff00ff, (ARGB)0xffff0000,
                                 WrapModeTile, &line);
    expect(Ok, status);
    status = GdipCreatePen2((GpBrush*)line, 10.0f, UnitPixel, &pen);
    expect(Ok, status);
    status = GdipGetPenFillType(pen, &type);
    expect(Ok, status);
    expect(PenTypeLinearGradient, type);
    GdipDeletePen(pen);
    GdipDeleteBrush((GpBrush*)line);
}

static void test_compoundarray(void)
{
    GpStatus status;
    GpPen *pen;
    static const REAL testvalues[] = {0.2, 0.4, 0.6, 0.8};
    INT count;

    status = GdipSetPenCompoundArray(NULL, testvalues, 4);
    expect(InvalidParameter, status);

    status = GdipCreatePen1((ARGB)0xffff00ff, 10.0f, UnitPixel, &pen);
    expect(Ok, status);

    status = GdipGetPenCompoundCount(NULL, NULL);
    expect(InvalidParameter, status);

    status = GdipGetPenCompoundCount(pen, NULL);
    expect(InvalidParameter, status);

    count = 10;
    status = GdipGetPenCompoundCount(pen, &count);
todo_wine {
    expect(Ok, status);
    ok(count == 0, "Unexpected compound count %d\n", count);
}
    status = GdipSetPenCompoundArray(pen, NULL, 0);
    expect(InvalidParameter, status);
    status = GdipSetPenCompoundArray(pen, NULL, 4);
    expect(InvalidParameter, status);
    status = GdipSetPenCompoundArray(pen, testvalues, 3);
    expect(InvalidParameter, status);
    status = GdipSetPenCompoundArray(pen, testvalues, 0);
    expect(InvalidParameter, status);
    status = GdipSetPenCompoundArray(pen, testvalues, -2);
    expect(InvalidParameter, status);

    status = GdipSetPenCompoundArray(pen, testvalues, 4);
    todo_wine expect(Ok, status);
    status = GdipSetPenCompoundArray(pen, NULL, 0);
    expect(InvalidParameter, status);

    count = 0;
    status = GdipGetPenCompoundCount(pen, &count);
todo_wine {
    expect(Ok, status);
    ok(count == 4, "Unexpected compound count %d\n", count);
}
    GdipDeletePen(pen);
}

static void get_pen_transform(GpPen *pen, REAL *values)
{
    GpMatrix *matrix;
    GpStatus status;

    status = GdipCreateMatrix(&matrix);
    expect(Ok, status);

    status = GdipGetPenTransform(pen, matrix);
    expect(Ok, status);
    status = GdipGetMatrixElements(matrix, values);
    expect(Ok, status);

    GdipDeleteMatrix(matrix);
}

static void test_transform(void)
{
    GpStatus status;
    GpPen *pen;
    GpMatrix *matrix, *matrix2;
    REAL values[6];

    status = GdipCreatePen1((ARGB)0xffff00ff, 10.0f, UnitPixel, &pen);
    expect(Ok, status);

    status = GdipCreateMatrix(&matrix);
    expect(Ok, status);

    status = GdipGetPenTransform(pen, matrix);
    expect(Ok, status);

    status = GdipGetMatrixElements(matrix, values);
    expect(Ok, status);

    expectf(1.0, values[0]);
    expectf(0.0, values[1]);
    expectf(0.0, values[2]);
    expectf(1.0, values[3]);
    expectf(0.0, values[4]);
    expectf(0.0, values[5]);

    GdipCreateMatrix2(3.0, -2.0, 5.0, 2.0, 6.0, 3.0, &matrix2);
    status = GdipSetPenTransform(pen, matrix2);
    expect(Ok, status);
    GdipDeleteMatrix(matrix2);

    status = GdipGetPenTransform(pen, matrix);
    expect(Ok, status);
    status = GdipGetMatrixElements(matrix, values);
    expect(Ok, status);
    expectf(3.0,  values[0]);
    expectf(-2.0,  values[1]);
    expectf(5.0,  values[2]);
    expectf(2.0, values[3]);
    expectf(6.0, values[4]);
    expectf(3.0,  values[5]);

    /* Translate */
    status = GdipTranslatePenTransform(NULL, 1.0, -2.0, MatrixOrderAppend);
    expect(InvalidParameter, status);

    status = GdipTranslatePenTransform(pen, 1.0, -2.0, MatrixOrderAppend);
    expect(Ok, status);

    status = GdipGetPenTransform(pen, matrix);
    expect(Ok, status);
    status = GdipGetMatrixElements(matrix, values);
    expect(Ok, status);
    expectf(3.0,  values[0]);
    expectf(-2.0,  values[1]);
    expectf(5.0,  values[2]);
    expectf(2.0, values[3]);
    expectf(7.0, values[4]);
    expectf(1.0,  values[5]);

    status = GdipTranslatePenTransform(pen, -3.0, 5.0, MatrixOrderPrepend);
    expect(Ok, status);

    status = GdipGetPenTransform(pen, matrix);
    expect(Ok, status);
    status = GdipGetMatrixElements(matrix, values);
    expect(Ok, status);
    expectf(3.0,  values[0]);
    expectf(-2.0,  values[1]);
    expectf(5.0,  values[2]);
    expectf(2.0, values[3]);
    expectf(23.0, values[4]);
    expectf(17.0,  values[5]);

    status = GdipResetPenTransform(pen);
    expect(Ok, status);

    status = GdipGetPenTransform(pen, matrix);
    expect(Ok, status);
    status = GdipGetMatrixElements(matrix, values);
    expect(Ok, status);

    expectf(1.0, values[0]);
    expectf(0.0, values[1]);
    expectf(0.0, values[2]);
    expectf(1.0, values[3]);
    expectf(0.0, values[4]);
    expectf(0.0, values[5]);

    /* Scale */
    status = GdipScalePenTransform(NULL, 1.0, 1.0, MatrixOrderPrepend);
    expect(InvalidParameter, status);

    status = GdipScalePenTransform(pen, 1.0, 1.0, MatrixOrderPrepend);
    expect(Ok, status);

    get_pen_transform(pen, values);
    expectf(1.0, values[0]);
    expectf(0.0, values[1]);
    expectf(0.0, values[2]);
    expectf(1.0, values[3]);
    expectf(0.0, values[4]);
    expectf(0.0, values[5]);

    status = GdipScalePenTransform(pen, 2.0, -10.0, MatrixOrderPrepend);
    expect(Ok, status);

    get_pen_transform(pen, values);
    expectf(2.0, values[0]);
    expectf(0.0, values[1]);
    expectf(0.0, values[2]);
    expectf(-10.0, values[3]);
    expectf(0.0, values[4]);
    expectf(0.0, values[5]);

    status = GdipScalePenTransform(pen, 2.0, -10.0, MatrixOrderAppend);
    expect(Ok, status);

    get_pen_transform(pen, values);
    expectf(4.0, values[0]);
    expectf(0.0, values[1]);
    expectf(0.0, values[2]);
    expectf(100.0, values[3]);
    expectf(0.0, values[4]);
    expectf(0.0, values[5]);

    status = GdipTranslatePenTransform(pen, 1.0, -2.0, MatrixOrderAppend);
    expect(Ok, status);

    get_pen_transform(pen, values);
    expectf(4.0, values[0]);
    expectf(0.0, values[1]);
    expectf(0.0, values[2]);
    expectf(100.0, values[3]);
    expectf(1.0, values[4]);
    expectf(-2.0, values[5]);

    status = GdipScalePenTransform(pen, 2.0, -10.0, MatrixOrderPrepend);
    expect(Ok, status);

    get_pen_transform(pen, values);
    expectf(8.0, values[0]);
    expectf(0.0, values[1]);
    expectf(0.0, values[2]);
    expectf(-1000.0, values[3]);
    expectf(1.0, values[4]);
    expectf(-2.0, values[5]);

    /* Multiply */
    status = GdipResetPenTransform(pen);
    expect(Ok, status);

    status = GdipSetMatrixElements(matrix, 2.0, 1.0, 1.0, 4.0, 1.0, 2.0);
    expect(Ok, status);

    status = GdipMultiplyPenTransform(NULL, matrix, MatrixOrderPrepend);
    expect(InvalidParameter, status);

    status = GdipMultiplyPenTransform(pen, matrix, MatrixOrderPrepend);
    expect(Ok, status);

    get_pen_transform(pen, values);
    expectf(2.0, values[0]);
    expectf(1.0, values[1]);
    expectf(1.0, values[2]);
    expectf(4.0, values[3]);
    expectf(1.0, values[4]);
    expectf(2.0, values[5]);

    status = GdipScalePenTransform(pen, 2.0, -10.0, MatrixOrderAppend);
    expect(Ok, status);

    status = GdipMultiplyPenTransform(pen, matrix, MatrixOrderAppend);
    expect(Ok, status);

    get_pen_transform(pen, values);
    expectf(-2.0, values[0]);
    expectf(-36.0, values[1]);
    expectf(-36.0, values[2]);
    expectf(-158.0, values[3]);
    expectf(-15.0, values[4]);
    expectf(-76.0, values[5]);

    /* Rotate */
    status = GdipResetPenTransform(pen);
    expect(Ok, status);

    status = GdipSetMatrixElements(matrix, 2.0, 1.0, 1.0, 4.0, 1.0, 2.0);
    expect(Ok, status);

    status = GdipSetPenTransform(pen, matrix);
    expect(Ok, status);

    status = GdipRotatePenTransform(NULL, 10.0, MatrixOrderPrepend);
    expect(InvalidParameter, status);

    status = GdipRotatePenTransform(pen, 45.0, MatrixOrderPrepend);
    expect(Ok, status);

    get_pen_transform(pen, values);
    expectf(2.12, values[0]);
    expectf(3.54, values[1]);
    expectf(-0.71, values[2]);
    expectf(2.12, values[3]);
    expectf(1.0, values[4]);
    expectf(2.0, values[5]);

    status = GdipScalePenTransform(pen, 2.0, -10.0, MatrixOrderAppend);
    expect(Ok, status);

    status = GdipRotatePenTransform(pen, 180.0, MatrixOrderAppend);
    expect(Ok, status);

    get_pen_transform(pen, values);
    expectf(-4.24, values[0]);
    expectf(35.36, values[1]);
    expectf(1.41, values[2]);
    expectf(21.21, values[3]);
    expectf(-2.0, values[4]);
    expectf(20.0, values[5]);

    GdipDeletePen(pen);

    GdipDeleteMatrix(matrix);
}

START_TEST(pen)
{
    struct GdiplusStartupInput gdiplusStartupInput;
    ULONG_PTR gdiplusToken;
    HMODULE hmsvcrt;
    int (CDECL * _controlfp_s)(unsigned int *cur, unsigned int newval, unsigned int mask);

    /* Enable all FP exceptions except _EM_INEXACT, which gdi32 can trigger */
    hmsvcrt = LoadLibraryA("msvcrt");
    _controlfp_s = (void*)GetProcAddress(hmsvcrt, "_controlfp_s");
    if (_controlfp_s) _controlfp_s(0, 0, 0x0008001e);

    test_startup();

    gdiplusStartupInput.GdiplusVersion              = 1;
    gdiplusStartupInput.DebugEventCallback          = NULL;
    gdiplusStartupInput.SuppressBackgroundThread    = 0;
    gdiplusStartupInput.SuppressExternalCodecs      = 0;

    GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL);

    test_constructor_destructor();
    test_constructor_destructor2();
    test_brushfill();
    test_dasharray();
    test_customcap();
    test_penfilltype();
    test_compoundarray();
    test_transform();

    GdiplusShutdown(gdiplusToken);
}
