//-----------------------------------------------------------------------------
// Implementation of our Request class; a request is a user-created thing
// that will generate an entity (line, curve) when the sketch is generated,
// in the same way that other entities are generated automatically, like
// by an extrude or a step and repeat.
//
// Copyright 2008-2013 Jonathan Westhues.
//-----------------------------------------------------------------------------
#include "solvespace.h"

const hRequest Request::HREQUEST_REFERENCE_XY = { 1 };
const hRequest Request::HREQUEST_REFERENCE_YZ = { 2 };
const hRequest Request::HREQUEST_REFERENCE_ZX = { 3 };

struct EntReqMapping {
    Request::Type  reqType;
    Entity::Type   entType;
    int            points;
    bool           useExtraPoints;
    bool           hasNormal;
    bool           hasDistance;
};
static const EntReqMapping EntReqMap[] = {
// request type                   entity type                 pts   xtra?   norml   dist
{ Request::Type::WORKPLANE,       Entity::Type::WORKPLANE,      1,  false,  true,   false },
{ Request::Type::DATUM_POINT,     (Entity::Type)0,              1,  false,  false,  false },
{ Request::Type::LINE_SEGMENT,    Entity::Type::LINE_SEGMENT,   2,  false,  false,  false },
{ Request::Type::CUBIC,           Entity::Type::CUBIC,          4,  true,   false,  false },
{ Request::Type::CUBIC_PERIODIC,  Entity::Type::CUBIC_PERIODIC, 3,  true,   false,  false },
{ Request::Type::CIRCLE,          Entity::Type::CIRCLE,         1,  false,  true,   true  },
{ Request::Type::ARC_OF_CIRCLE,   Entity::Type::ARC_OF_CIRCLE,  3,  false,  true,   false },
{ Request::Type::TTF_TEXT,        Entity::Type::TTF_TEXT,       4,  false,  true,   false },
};

static void CopyEntityInfo(const EntReqMapping *te, int extraPoints,
                           Entity::Type *ent, Request::Type *req,
                           int *pts, bool *hasNormal, bool *hasDistance)
{
    int points = te->points;
    if(te->useExtraPoints) points += extraPoints;

    if(ent)         *ent         = te->entType;
    if(req)         *req         = te->reqType;
    if(pts)         *pts         = points;
    if(hasNormal)   *hasNormal   = te->hasNormal;
    if(hasDistance) *hasDistance = te->hasDistance;
}

bool EntReqTable::GetRequestInfo(Request::Type req, int extraPoints,
                                 Entity::Type *ent, int *pts, bool *hasNormal, bool *hasDistance)
{
    for(const EntReqMapping &te : EntReqMap) {
        if(req == te.reqType) {
            CopyEntityInfo(&te, extraPoints, ent, NULL, pts, hasNormal, hasDistance);
            return true;
        }
    }
    return false;
}

bool EntReqTable::GetEntityInfo(Entity::Type ent, int extraPoints,
                                Request::Type *req, int *pts, bool *hasNormal, bool *hasDistance)
{
    for(const EntReqMapping &te : EntReqMap) {
        if(ent == te.entType) {
            CopyEntityInfo(&te, extraPoints, NULL, req, pts, hasNormal, hasDistance);
            return true;
        }
    }
    return false;
}

Request::Type EntReqTable::GetRequestForEntity(Entity::Type ent) {
    Request::Type req;
    ssassert(GetEntityInfo(ent, 0, &req, NULL, NULL, NULL),
             "No entity for request");
    return req;
}

void Request::Generate(IdList<Entity,hEntity> *entity,
                       IdList<Param,hParam> *param)
{
    int points = 0;
    Entity::Type et = (Entity::Type)0;
    bool hasNormal = false;
    bool hasDistance = false;
    int i;

    // Request-specific generation.
    switch(type) {
        case Type::TTF_TEXT: {
            double actualAspectRatio = SS.fonts.AspectRatio(font, str);
            if(EXACT(actualAspectRatio != 0.0)) {
                // We could load the font, so use the actual value.
                aspectRatio = actualAspectRatio;
            }
            if(EXACT(aspectRatio == 0.0)) {
                // We couldn't load the font and we don't have anything saved,
                // so just use 1:1, which is valid for the missing font symbol anyhow.
                aspectRatio = 1.0;
            }
            break;
        }

        default: // most requests don't do anything else
            break;
    }

    Entity e = {};
    EntReqTable::GetRequestInfo(type, extraPoints, &et, &points, &hasNormal, &hasDistance);

    // Generate the entity that's specific to this request.
    e.type = et;
    e.extraPoints = extraPoints;
    e.group = group;
    e.style = style;
    e.workplane = workplane;
    e.construction = construction;
    e.str = str;
    e.font = font;
    e.aspectRatio = aspectRatio;
    e.h = h.entity(0);

    // And generate entities for the points
    for(i = 0; i < points; i++) {
        Entity p = {};
        p.workplane = workplane;
        // points start from entity 1, except for datum point case
        p.h = h.entity(i+((et != (Entity::Type)0) ? 1 : 0));
        p.group = group;
        p.style = style;
        if(type == Request::Type::ARC_OF_CIRCLE && i == 0) {
            // mark arc center point as construction, since it shouldn't be included
            // in bounding box calculation
            p.construction = true;
        }

        if(workplane.v == Entity::FREE_IN_3D.v) {
            p.type = Entity::Type::POINT_IN_3D;
            // params for x y z
            p.param[0] = AddParam(param, h.param(16 + 3*i + 0));
            p.param[1] = AddParam(param, h.param(16 + 3*i + 1));
            p.param[2] = AddParam(param, h.param(16 + 3*i + 2));
        } else {
            p.type = Entity::Type::POINT_IN_2D;
            // params for u v
            p.param[0] = AddParam(param, h.param(16 + 3*i + 0));
            p.param[1] = AddParam(param, h.param(16 + 3*i + 1));
        }
        entity->Add(&p);
        e.point[i] = p.h;
    }
    if(hasNormal) {
        Entity n = {};
        n.workplane = workplane;
        n.h = h.entity(32);
        n.group = group;
        n.style = style;
        if(workplane.v == Entity::FREE_IN_3D.v) {
            n.type = Entity::Type::NORMAL_IN_3D;
            n.param[0] = AddParam(param, h.param(32+0));
            n.param[1] = AddParam(param, h.param(32+1));
            n.param[2] = AddParam(param, h.param(32+2));
            n.param[3] = AddParam(param, h.param(32+3));
        } else {
            n.type = Entity::Type::NORMAL_IN_2D;
            // and this is just a copy of the workplane quaternion,
            // so no params required
        }
        ssassert(points >= 1, "Positioning a normal requires a point");
        // The point determines where the normal gets displayed on-screen;
        // it's entirely cosmetic.
        n.point[0] = e.point[0];
        entity->Add(&n);
        e.normal = n.h;
    }
    if(hasDistance) {
        Entity d = {};
        d.workplane = workplane;
        d.h = h.entity(64);
        d.group = group;
        d.style = style;
        d.type = Entity::Type::DISTANCE;
        d.param[0] = AddParam(param, h.param(64));
        entity->Add(&d);
        e.distance = d.h;
    }

    if(et != (Entity::Type)0) entity->Add(&e);
}

std::string Request::DescriptionString() const {
    const char *s = "";
    if(h.v == Request::HREQUEST_REFERENCE_XY.v) {
        s = "#XY";
    } else if(h.v == Request::HREQUEST_REFERENCE_YZ.v) {
        s = "#YZ";
    } else if(h.v == Request::HREQUEST_REFERENCE_ZX.v) {
        s = "#ZX";
    } else {
        switch(type) {
            case Type::WORKPLANE:       s = "workplane";      break;
            case Type::DATUM_POINT:     s = "datum-point";    break;
            case Type::LINE_SEGMENT:    s = "line-segment";   break;
            case Type::CUBIC:           s = "cubic-bezier";   break;
            case Type::CUBIC_PERIODIC:  s = "periodic-cubic"; break;
            case Type::CIRCLE:          s = "circle";         break;
            case Type::ARC_OF_CIRCLE:   s = "arc-of-circle;";  break;
            case Type::TTF_TEXT:        s = "ttf-text";       break;
        }
    }
    ssassert(s != NULL, "Unexpected request type");
    return ssprintf("r%03x-%s", h.v, s);
}

int Request::IndexOfPoint(hEntity he) const {
    if(type == Type::DATUM_POINT) {
        return (he.v == h.entity(0).v) ? 0 : -1;
    }
    for(int i = 0; i < MAX_POINTS_IN_ENTITY; i++) {
        if(he.v == h.entity(i + 1).v) {
            return i;
        }
    }
    return -1;
}

hParam Request::AddParam(IdList<Param,hParam> *param, hParam hp) {
    Param pa = {};
    pa.h = hp;
    param->Add(&pa);
    return hp;
}

