/**********************************Module**********************************\
*
* ssflwbox.c
*
* 3D FlowerBox screen saver
*
* Copyright (c) 1995 Microsoft Corporation
*
\**************************************************************************/

#include "precomp.h"
#pragma hdrstop

// Minimum and maximum image sizes
#define MINIMAGESIZE 10
#define MAXIMAGESIZE 100

// Color tables for checkboard, per-side and single color modes
GLfloat base_checker_cols[MAXSIDES][NCCOLS][4] =
{
    1.0f, 0.0f, 0.0f, 1.0f,
    0.0f, 1.0f, 0.0f, 1.0f,
    0.0f, 1.0f, 0.0f, 1.0f,
    0.0f, 0.0f, 1.0f, 1.0f,
    0.0f, 0.0f, 1.0f, 1.0f,
    1.0f, 0.0f, 1.0f, 1.0f,
    1.0f, 0.0f, 1.0f, 1.0f,
    0.0f, 1.0f, 1.0f, 1.0f,
    0.0f, 1.0f, 1.0f, 1.0f,
    1.0f, 1.0f, 0.0f, 1.0f,
    1.0f, 1.0f, 0.0f, 1.0f,
    0.5f, 0.5f, 1.0f, 1.0f,
    0.5f, 0.5f, 1.0f, 1.0f,
    1.0f, 0.5f, 0.5f, 1.0f,
    1.0f, 0.5f, 0.5f, 1.0f,
    1.0f, 0.0f, 0.0f, 1.0f
};
GLfloat checker_cols[MAXSIDES][NCCOLS][4];

GLfloat base_side_cols[MAXSIDES][4] =
{
    1.0f, 0.0f, 0.0f, 1.0f,
    0.0f, 1.0f, 0.0f, 1.0f,
    0.0f, 0.0f, 1.0f, 1.0f,
    1.0f, 0.0f, 1.0f, 1.0f,
    0.0f, 1.0f, 1.0f, 1.0f,
    1.0f, 1.0f, 0.0f, 1.0f,
    0.5f, 0.5f, 1.0f, 1.0f,
    1.0f, 0.5f, 0.5f, 1.0f
};
GLfloat side_cols[MAXSIDES][4];

GLfloat base_solid_cols[4] =
{
    1.0f, 1.0f, 1.0f, 1.0f
};
GLfloat solid_cols[4];

// Current geometry
GEOMETRY *cur_geom;

// Set when a rendering context is available
BOOL gbGlInit = FALSE;

// Common library context
SSContext gssc;

// Spin rotations
double xr = 0, yr = 0, zr = 0;
// Scale factor and increment
FLT sf;
FLT sfi;
// Color cycling hue phase
FLT phase = 0.0f;

// Default configuration
CONFIG config =
{
    TRUE, FALSE, FALSE, TRUE, TRUE, MAXSUBDIV, ID_COL_PER_SIDE,
    (MAXIMAGESIZE+MINIMAGESIZE)/2, GEOM_CUBE, GL_FRONT
};

// A slider range
typedef struct _RANGE
{
    int min_val;
    int max_val;
    int step;
    int page_step;
} RANGE;

RANGE complexity_range = {MINSUBDIV, MAXSUBDIV, 1, 2};
RANGE image_size_range = {MINIMAGESIZE, MAXIMAGESIZE, 1, 10};

// True if the current OpenGL version is 1.1
BOOL bOgl11;

// True if checkered mode is on
BOOL bCheckerOn;

/******************************Public*Routine******************************\
*
* dprintf
*
* Debug output printf
*
\**************************************************************************/

#if DBG
void dprintf_out(char *fmt, ...)
{
    va_list args;
    char dbg[256];

    va_start(args, fmt);
    vsprintf(dbg, fmt, args);
    va_end(args);
    OutputDebugStringA(dbg);
}
#endif

/******************************Public*Routine******************************\
*
* assert_failed
*
* Assertion failure handler
*
\**************************************************************************/

#if DBG
void assert_failed(char *file, int line, char *msg)
{
    dprintf(("Assertion failed %s(%d): %s\n", file, line, msg));
    DebugBreak();
}
#endif

/******************************Public*Routine******************************\
*
* V3Len
*
* Vector length
*
\**************************************************************************/

FLT V3Len(PT3 *v)
{
    return (FLT)sqrt(v->x*v->x+v->y*v->y+v->z*v->z);
}

/******************************Public*Routine******************************\
*
* ComputeHsvColors
*
* Compute a smooth range of colors depending on the current color mode
*
\**************************************************************************/

void ComputeHsvColors(void)
{
    GLfloat *cols;
    int ncols;
    FLT ang, da;
    int hex;
    FLT fhex, frac;
    FLT p, q, t;
    FLT sat, val;

    switch(config.color_pick)
    {
    case ID_COL_CHECKER:
        ncols = MAXSIDES*NCCOLS;
        cols = &checker_cols[0][0][0];
        break;
    case ID_COL_PER_SIDE:
        ncols = MAXSIDES;
        cols = &side_cols[0][0];
        break;
    case ID_COL_SINGLE:
        ncols = 1;
        cols = &solid_cols[0];
        break;
    }

    ang = phase;
    da = (FLT)((2*PI)/ncols);
    val = sat = 1.0f;

    while (ncols > 0)
    {
        fhex = (FLT)(6*ang/(2*PI));
        hex = (int)fhex;
        frac = fhex-hex;
        hex = hex % 6;
        
	p = val*(1-sat);
	q = val*(1-sat*frac);
	t = val*(1-sat*(1-frac));
        
	switch(hex)
	{
	case 0:
            cols[0] = val;
            cols[1] = t;
            cols[2] = p;
	    break;
	case 1:
            cols[0] = q;
            cols[1] = val;
            cols[2] = p;
	    break;
	case 2:
            cols[0] = p;
            cols[1] = val;
            cols[2] = t;
	    break;
	case 3:
            cols[0] = p;
            cols[1] = q;
            cols[2] = val;
	    break;
	case 4:
            cols[0] = t;
            cols[1] = p;
            cols[2] = val;
            break;
	case 5:
            cols[0] = val;
            cols[1] = p;
            cols[2] = q;
	    break;
	}

        ang += da;
        cols += 4;
        ncols--;
    }
}

/******************************Public*Routine******************************\
*
* Draw
*
* Draw everything
*
\**************************************************************************/

void Draw(void)
{
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    glLoadIdentity();

    glRotated(xr, 1, 0, 0);
    glRotated(yr, 0, 1, 0);
    glRotated(zr, 0, 0, 1);

    DrawGeom(cur_geom);

    glFlush();
}

/******************************Public*Routine******************************\
*
* Update
*
* Update all varying values, called by the common library
*
\**************************************************************************/

void Update(void *data)
{
    if (config.spin)
    {
        xr += 3;
        yr += 2;
    }

    if (config.bloom)
    {
        sf += sfi;
        if (sf > cur_geom->max_sf ||
            sf < cur_geom->min_sf)
        {
            sfi = -sfi;
        }
        UpdatePts(cur_geom, sf);
    }

    if (config.cycle_colors)
    {
        ComputeHsvColors();
        phase += (FLT)(2.5*PI/180.);
    }
    
    Draw();
}

// String storage
TCHAR geom_names[IDS_GEOM_COUNT][20];

/******************************Public*Routine******************************\
* getIniSettings
*
* Get the screen saver configuration options from .INI file/registry.
*
\**************************************************************************/

static void 
getIniSettings()
{
    // Get registry settings

    if( ! ss_RegistrySetup( GetModuleHandle(NULL), IDS_INI_SECTION, 
                            IDS_INIFILE ) )
        return;
    
    config.smooth_colors =
        ss_GetRegistryInt( IDS_CONFIG_SMOOTH_COLORS, config.smooth_colors );
    config.triangle_colors =
        ss_GetRegistryInt( IDS_CONFIG_TRIANGLE_COLORS, config.triangle_colors );
    config.cycle_colors =
        ss_GetRegistryInt( IDS_CONFIG_CYCLE_COLORS, config.cycle_colors );
    config.spin =
        ss_GetRegistryInt( IDS_CONFIG_SPIN, config.spin );
    config.bloom =
        ss_GetRegistryInt( IDS_CONFIG_BLOOM, config.bloom );
    config.subdiv =
        ss_GetRegistryInt( IDS_CONFIG_SUBDIV, config.subdiv );
    config.color_pick =
        ss_GetRegistryInt( IDS_CONFIG_COLOR_PICK, config.color_pick );
    config.image_size =
        ss_GetRegistryInt( IDS_CONFIG_IMAGE_SIZE, config.image_size );
    config.geom =
        ss_GetRegistryInt( IDS_CONFIG_GEOM, config.geom );
    config.two_sided =
        ss_GetRegistryInt( IDS_CONFIG_TWO_SIDED, config.two_sided );
}

/******************************Public*Routine******************************\
* saveIniSettings
*
* Save the screen saver configuration option to the .INI file/registry.
*
\**************************************************************************/

static void 
saveIniSettings()
{
    if( ! ss_RegistrySetup( GetModuleHandle(NULL), IDS_INI_SECTION, 
                            IDS_INIFILE ) )
        return;

    ss_WriteRegistryInt( IDS_CONFIG_SMOOTH_COLORS, config.smooth_colors );

    ss_WriteRegistryInt( IDS_CONFIG_TRIANGLE_COLORS, config.triangle_colors );
    ss_WriteRegistryInt( IDS_CONFIG_CYCLE_COLORS, config.cycle_colors );
    ss_WriteRegistryInt( IDS_CONFIG_SPIN, config.spin );
    ss_WriteRegistryInt( IDS_CONFIG_BLOOM, config.bloom );
    ss_WriteRegistryInt( IDS_CONFIG_SUBDIV, config.subdiv );
    ss_WriteRegistryInt( IDS_CONFIG_COLOR_PICK, config.color_pick );
    ss_WriteRegistryInt( IDS_CONFIG_IMAGE_SIZE, config.image_size );
    ss_WriteRegistryInt( IDS_CONFIG_GEOM, config.geom );
    ss_WriteRegistryInt( IDS_CONFIG_TWO_SIDED, config.two_sided );
}

/******************************Public*Routine******************************\
*
* NewConfig
*
* Set up a new configuration
*
\**************************************************************************/

void NewConfig(CONFIG *cnf)
{
    // Set new config
    config = *cnf;

    // Save to ini file
    saveIniSettings();
    
    // Reset colors
    memcpy(checker_cols, base_checker_cols, sizeof(checker_cols));
    memcpy(side_cols, base_side_cols, sizeof(side_cols));
    memcpy(solid_cols, base_solid_cols, sizeof(solid_cols));

    // Reset geometry
    cur_geom = geom_table[config.geom];
    cur_geom->init(cur_geom);
    if (bOgl11 && !bCheckerOn) DrawWithVArrays (cur_geom);
    
    assert(cur_geom->total_pts <= MAXPTS);
           
    InitVlen(cur_geom, cur_geom->total_pts, cur_geom->pts);
    sf = 0.0f;
    sfi = cur_geom->sf_inc;
    UpdatePts(cur_geom, sf);

    // Reset OpenGL parameters according to configuration
    // Only done if GL has been initialized
    if (gbGlInit)
    {
        GLfloat fv4[4];
        
        if (config.two_sided == GL_FRONT_AND_BACK)
        {
            glLightModeli(GL_LIGHT_MODEL_TWO_SIDE, GL_TRUE);
            glDisable(GL_CULL_FACE);
        }
        else
        {
            glLightModeli(GL_LIGHT_MODEL_TWO_SIDE, GL_FALSE);
            glEnable(GL_CULL_FACE);
        }
        
        fv4[0] = fv4[1] = fv4[2] = .8f;
        fv4[3] = 1.0f;
        glMaterialfv(config.two_sided, GL_SPECULAR, fv4);
        glMaterialf(config.two_sided, GL_SHININESS, 30.0f);
    }
}

/******************************Public*Routine******************************\
*
* RegisterDialogClasses
*
* Standard screensaver hook
*
\**************************************************************************/

BOOL WINAPI RegisterDialogClasses(HANDLE hinst)
{
    return TRUE;
}

// Temporary configuration for when the configuration dialog is active
// If the dialog is ok'ed then this becomes the current configuration,
// otherwise it is discarded
CONFIG temp_config;

/******************************Public*Routine******************************\
*
* ScreenSaverConfigureDialog
*
* Standard screensaver hook
*
\**************************************************************************/

BOOL CALLBACK ScreenSaverConfigureDialog(HWND hdlg, UINT msg,
                                         WPARAM wpm, LPARAM lpm)
{
    HWND hCtrl;
    int i;
    
    switch(msg)
    {
    case WM_INITDIALOG:

        InitCommonControls();

        getIniSettings();
    
        temp_config = config;
        
        CheckRadioButton(hdlg, ID_COL_PICK_FIRST, ID_COL_PICK_LAST,
                         config.color_pick);
        CheckDlgButton(hdlg, ID_COL_SMOOTH, config.smooth_colors);
        CheckDlgButton(hdlg, ID_COL_TRIANGLE, config.triangle_colors);
        CheckDlgButton(hdlg, ID_COL_CYCLE, config.cycle_colors);
        CheckDlgButton(hdlg, ID_SPIN, config.spin);
        CheckDlgButton(hdlg, ID_BLOOM, config.bloom);
        CheckDlgButton(hdlg, ID_TWO_SIDED,
                       config.two_sided == GL_FRONT_AND_BACK);
        
        ss_SetupTrackbar( hdlg, ID_COMPLEXITY, MINSUBDIV, MAXSUBDIV, 
                          complexity_range.step,
                          complexity_range.page_step,
                          config.subdiv);

        ss_SetupTrackbar( hdlg, ID_IMAGE_SIZE, MINIMAGESIZE, MAXIMAGESIZE, 
                          image_size_range.step,
                          image_size_range.page_step,
                          config.image_size);

        hCtrl = GetDlgItem(hdlg, ID_GEOM);
        SendMessage(hCtrl, CB_RESETCONTENT, 0, 0);
        for (i = 0; i < IDS_GEOM_COUNT; i++)
        {
            LoadString( hMainInstance, i+IDS_GEOM_FIRST, geom_names[i],
                        sizeof(geom_names)/IDS_GEOM_COUNT );
            SendMessage(hCtrl, CB_ADDSTRING, 0, (LPARAM)geom_names[i]);
        }
        SendMessage(hCtrl, CB_SETCURSEL, config.geom, 0);
        
        SetFocus(GetDlgItem(hdlg, ID_COMPLEXITY));
        return FALSE;

    case WM_COMMAND:
        switch(LOWORD(wpm))
        {
        case ID_COL_CHECKER:
        case ID_COL_PER_SIDE:
        case ID_COL_SINGLE:
            temp_config.color_pick = LOWORD(wpm);
            break;

        case ID_COL_SMOOTH:
            temp_config.smooth_colors = !temp_config.smooth_colors;
            break;
        case ID_COL_TRIANGLE:
            temp_config.triangle_colors = !temp_config.triangle_colors;
            break;
        case ID_COL_CYCLE:
            temp_config.cycle_colors = !temp_config.cycle_colors;
            break;
            
        case ID_SPIN:
            temp_config.spin = !temp_config.spin;
            break;
        case ID_BLOOM:
            temp_config.bloom = !temp_config.bloom;
            break;
        case ID_TWO_SIDED:
            temp_config.two_sided =
                temp_config.two_sided == GL_FRONT_AND_BACK ? GL_FRONT :
                GL_FRONT_AND_BACK;
            break;

        case IDOK:
            temp_config.subdiv =
                ss_GetTrackbarPos(hdlg, ID_COMPLEXITY);
            temp_config.image_size =
                ss_GetTrackbarPos(hdlg, ID_IMAGE_SIZE);
            temp_config.geom =
                SendMessage(GetDlgItem(hdlg, ID_GEOM), CB_GETCURSEL, 0, 0);
            NewConfig(&temp_config);
            // Fall through
        case IDCANCEL:
            EndDialog(hdlg, LOWORD(wpm));
            break;
        }
        return TRUE;
        
    }

    return FALSE;
}

/******************************Public*Routine******************************\
*
* Init
*
* Drawing initialization, called by common library
*
\**************************************************************************/

void Init(void *data)
{
    GLfloat fv4[4];

    gbGlInit = TRUE;
    
    bOgl11 = ss_fOnGL11();

    if (config.color_pick == ID_COL_CHECKER) bCheckerOn = TRUE;
    else bCheckerOn = FALSE;

    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    gluPerspective(45, 1, 2, 5); // for object range -1.5 to 1.5
    gluLookAt(0, 0, 3.5, 0, 0, 0, 0, 1, 0);
    glMatrixMode(GL_MODELVIEW);

    glEnable(GL_DEPTH_TEST);
    glClearDepth(1);

    glCullFace(GL_BACK);
    
    fv4[0] = 2.0f;
    fv4[1] = 2.0f;
    fv4[2] = 10.0f;
    fv4[3] = 1.0f;
    glLightfv(GL_LIGHT0, GL_POSITION, fv4);
    
    glEnable(GL_LIGHTING);
    glEnable(GL_LIGHT0);

    glEnable(GL_NORMALIZE);
    
    // Make default configuration current
    NewConfig(&config);
}

/******************************Public*Routine******************************\
* SetFloaterInfo
*
* Set the size and motion of the floating window
*
\**************************************************************************/
void
SetFloaterInfo( ISIZE *pParentSize, CHILD_INFO *pChild )
{
    float sizeFact;
    float sizeScale;
    int size;
    ISIZE *pChildSize = &pChild->size;
    MOTION_INFO *pMotion = &pChild->motionInfo;

    sizeScale = (float)config.image_size / 100.0f;
    sizeFact = 0.25f + (0.5f * sizeScale);     // range 25-75%
    size = (int) (sizeFact * ( ((float)(pParentSize->width + pParentSize->height )) / 2.0f ));
    SS_CLAMP_TO_RANGE2( size, 0, pParentSize->width );
    SS_CLAMP_TO_RANGE2( size, 0, pParentSize->height );

    pChildSize->width = pChildSize->height = size;

    // Floater motion
    pMotion->posInc.x = .005f * (float) size;
    if( pMotion->posInc.x < 1.0f )
        pMotion->posInc.x = 1.0f;
    pMotion->posInc.y = pMotion->posInc.x;
    pMotion->posIncVary.x = .4f * pMotion->posInc.x;
    pMotion->posIncVary.y = pMotion->posIncVary.x;
}

/******************************Public*Routine******************************\
*
* FloaterFail
*
* Called when the floating window cannot be created
*
\**************************************************************************/
void FloaterFail(void *data)
{
    HINSTANCE hinst;
    TCHAR error_str[20];
    TCHAR start_failed[80];

    hinst = GetModuleHandle(NULL);
    if (LoadString(hinst, IDS_ERROR,
                   error_str, sizeof(error_str)) &&
        LoadString(hinst, IDS_START_FAILED,
                   start_failed, sizeof(start_failed)))
    {
        MessageBox(NULL, start_failed, error_str, MB_OK);
    }
}

/******************************Public*Routine******************************\
*
* ss_Init
*
* Screensaver initialization routine, called at startup by common library
*
\**************************************************************************/

SSContext *ss_Init(void)
{
    getIniSettings();
    
    ss_InitFunc(Init);
    ss_UpdateFunc(Update);

    gssc.bFloater = TRUE;
    gssc.floaterInfo.bMotion = TRUE;
    gssc.floaterInfo.ChildSizeFunc = SetFloaterInfo;

    gssc.bDoubleBuf = TRUE;
    gssc.depthType = SS_DEPTH16;

    return &gssc;
}


/**************************************************************************\
* ConfigInit
*
* Dialog box version of ss_Init.  Used for setting up any gl drawing on the
* dialog.
*
\**************************************************************************/
BOOL
ss_ConfigInit( HWND hDlg )
{
    return TRUE;
}
