/*----------------------------------------------------------------------------
  ChucK Strongly-timed Audio Programming Language
    Compiler and Virtual Machine

  Copyright (c) 2003 Ge Wang and Perry R. Cook. All rights reserved.
    http://chuck.stanford.edu/
    http://chuck.cs.princeton.edu/

  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 2 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, write to the Free Software
  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
  U.S.A.
-----------------------------------------------------------------------------*/

//-----------------------------------------------------------------------------
// file: ulib_std.cpp
// desc: standard class library
//
// author: Ge Wang (ge@ccrma.stanford.edu | gewang@cs.princeton.edu)
// date: Spring 2004
//-----------------------------------------------------------------------------
#include "ulib_std.h"
#include "util_buffers.h"
#include "util_math.h"
#include "util_string.h"
#include "util_platforms.h"

#ifndef __DISABLE_PROMPTER__
#include "util_console.h"
#endif

#ifndef __DISABLE_THREADS__
#include "util_thread.h"
#endif

#include "chuck.h"
#include "chuck_type.h"
#include "chuck_compile.h"
#include "chuck_instr.h"

#include <stdlib.h>
#include <time.h>
#include <math.h>

#ifdef __PLATFORM_WINDOWS__
  #ifndef __CHUNREAL_ENGINE__
    #include <windows.h>
  #else
    // 1.5.0.0 (ge) | #chunreal
    // unreal engine on windows disallows including windows.h
    #include "Windows/MinWindows.h"
  #endif // #ifndef __CHUNREAL_ENGINE__
int setenv( const char *n, const char *v, int i )
{
  #ifndef __CHUNREAL_ENGINE__
    return !SetEnvironmentVariable(n, v);
  #else
    // #chunreal explicitly call ASCII version
    return !SetEnvironmentVariableA(n, v);
  #endif
}
#else
  #include <unistd.h>
#endif // #ifdef __PLATFORM_WINDOWS__

// for ConsoleInput and StringTokenizer
#include <sstream>
#include <iostream>
#ifndef __DISABLE_THREADS__
#include "util_thread.h"
#endif
using namespace std;


#ifndef __DISABLE_KBHIT__
// KBHit
CK_DLL_CTOR( KBHit_ctor );
CK_DLL_DTOR( KBHit_dtor );
CK_DLL_MFUN( KBHit_on );
CK_DLL_MFUN( KBHit_off );
CK_DLL_MFUN( KBHit_state );
CK_DLL_MFUN( KBHit_hit );
CK_DLL_MFUN( KBHit_more );
CK_DLL_MFUN( KBHit_getchar );
CK_DLL_MFUN( KBHit_can_wait );

static t_CKUINT KBHit_offset_data = 0;
#endif // __DISABLE_KBHIT__

#ifndef __DISABLE_PROMPTER__
// Skot functions
CK_DLL_CTOR( Skot_ctor );
CK_DLL_DTOR( Skot_dtor );
CK_DLL_MFUN( Skot_prompt );
CK_DLL_MFUN( Skot_prompt2 );
CK_DLL_MFUN( Skot_more );
CK_DLL_MFUN( Skot_getLine );
CK_DLL_MFUN( Skot_can_wait );

static t_CKUINT Skot_offset_data = 0;
#endif // __DISABLE_PROMPTER__

// StrTok functions
CK_DLL_CTOR( StrTok_ctor );
CK_DLL_DTOR( StrTok_dtor );
CK_DLL_MFUN( StrTok_set );
CK_DLL_MFUN( StrTok_reset );
CK_DLL_MFUN( StrTok_more );
CK_DLL_MFUN( StrTok_next );
CK_DLL_MFUN( StrTok_next2 );
CK_DLL_MFUN( StrTok_get );
CK_DLL_MFUN( StrTok_get2 );
CK_DLL_MFUN( StrTok_size );

static t_CKUINT StrTok_offset_data = 0;

#ifdef AJAY

#include <fstream>

// VCR functions
CK_DLL_CTOR( VCR_ctor );
CK_DLL_MFUN( VCR_load );
CK_DLL_MFUN( VCR_reset );
CK_DLL_MFUN( VCR_seek );
CK_DLL_MFUN( VCR_more );
CK_DLL_MFUN( VCR_curr );
CK_DLL_MFUN( VCR_next );
CK_DLL_MFUN( VCR_pos );
CK_DLL_MFUN( VCR_size );
CK_DLL_MFUN( VCR_name );

static t_CKUINT VCR_offset_data = 0;

// Cereal functions
CK_DLL_CTOR( Cereal_ctor );
CK_DLL_MFUN( Cereal_open );
CK_DLL_MFUN( Cereal_close );
CK_DLL_MFUN( Cereal_send );
CK_DLL_MFUN( Cereal_recv );
CK_DLL_MFUN( Cereal_more );

static t_CKUINT Cereal_offset_data = 0;

#endif


// variable to random max
static t_CKINT g_randMax = CK_RANDOM_MAX;


//-----------------------------------------------------------------------------
// name: libstd_query()
// desc: query entry point
//-----------------------------------------------------------------------------
DLL_QUERY libstd_query( Chuck_DL_Query * QUERY )
{
    // get global
    Chuck_Env * env = QUERY->env();
    // set name
    QUERY->setname( QUERY, "Std" );

    // register deprecate
    type_engine_register_deprecate( env, "std", "Std" );

    // begin class
    QUERY->begin_class( QUERY, "Std", "Object" );
    QUERY->doc_class( QUERY, "a standard library in ChucK, which includes utility functions for random number generation, unit conversions, and absolute value." );

    // add abs
    QUERY->add_sfun( QUERY, abs_impl, "int", "abs" );
    QUERY->add_arg( QUERY, "int", "value" );
    QUERY->doc_func( QUERY, "Return absolute value of integer." );
    
    // add fabs
    QUERY->add_sfun( QUERY, fabs_impl, "float", "fabs" );
    QUERY->add_arg( QUERY, "float", "value" );
    QUERY->doc_func( QUERY, "Return absolute value of float." );

    // add rand
    QUERY->add_sfun( QUERY, rand_impl, "int", "rand"); //! return int between 0 and max
    QUERY->doc_func( QUERY, "generate a random integer between 0 and Std.RAND_MAX. (NOTE: this is deprecated; use Math.random())." );

    // add rand2
    QUERY->add_sfun( QUERY, rand2_impl, "int", "rand2" ); //! integer between [min,max]
    QUERY->add_arg( QUERY, "int", "min" );
    QUERY->add_arg( QUERY, "int", "max" );
    QUERY->doc_func( QUERY, "Generate a random integer in range [min, max]. (NOTE: this is deprecated; use Math.random2())." );

    // add rand
    QUERY->add_sfun( QUERY, randf_impl, "float", "randf" ); //! rand between -1.0,1.0
    QUERY->doc_func( QUERY, "Generate random floating point number in the range [-1, 1]. (Note: this is deprecated; use Math.randomf())" );

    // add rand2
    QUERY->add_sfun( QUERY, rand2f_impl, "float", "rand2f" ); //! rand between min and max
    QUERY->add_arg( QUERY, "float", "min" );
    QUERY->add_arg( QUERY, "float", "max" );
    QUERY->doc_func( QUERY, "Generate random floating point number in the range [min, max]. (NOTE: this is deprecated; use Math.random2f())" );

    // add srand
    QUERY->add_sfun( QUERY, srand_impl, "void", "srand" );
    QUERY->add_arg( QUERY, "int", "seed" );
    QUERY->doc_func( QUERY, "seed the random number generator. Different seeds will likely generate different sequences of random numbers even if the seeds are close together; alternatively, a sequence of random numbers can be repeated by setting the same seed. (NOTE: this is deprecated; use Math.srandom())" );

    // random max
    QUERY->add_svar( QUERY, "int", "RAND_MAX", TRUE, &g_randMax );
    QUERY->doc_var( QUERY, "The largest possible value returned by Std.rand()." );

    // add sgn
    QUERY->add_sfun( QUERY, sgn_impl, "float", "sgn" ); //! return sign of value (-1, 0, 1)
    QUERY->add_arg( QUERY, "float", "value" );
    QUERY->doc_func( QUERY, "get sign of value as -1 (negative), 0, or 1 (positive)." );

    // add system
    //! see \example std.ck
    QUERY->add_sfun( QUERY, system_impl, "int", "system" ); //! issue system command
    QUERY->add_arg( QUERY, "string", "cmd" );
    QUERY->doc_func( QUERY, "pass a command to be executed in the shell (requires --caution-to-the-wind flag to be set)." );

    // add atoi
    QUERY->add_sfun( QUERY, atoi_impl, "int", "atoi" ); //! string to integer
    QUERY->add_arg( QUERY, "string", "value" );
    QUERY->doc_func( QUERY, "convert ascii (string) to integer (int)." );

    // add atof
    QUERY->add_sfun( QUERY, atof_impl, "float", "atof" ); //! string to float
    QUERY->add_arg( QUERY, "string", "value" );
    QUERY->doc_func( QUERY, "converts ascii (string) to floating point value (float)." );

    // add itoa
    QUERY->add_sfun( QUERY, itoa_impl, "string", "itoa" ); //! int to string
    QUERY->add_arg( QUERY, "int", "i" );
    QUERY->doc_func( QUERY, "converts integer (int) to ascii (string)." );

    // add ftoa
    QUERY->add_sfun( QUERY, ftoa_impl, "string", "ftoa" ); //! float to string
    QUERY->add_arg( QUERY, "float", "f" );
    QUERY->add_arg( QUERY, "int", "precision" );
    QUERY->doc_func( QUERY, "converts floating point value (float) to ascii (string) with specified precision (number of decimal digits)." );

    // add ftoi
    QUERY->add_sfun( QUERY, ftoi_impl, "int", "ftoi" ); //! float to int
    QUERY->add_arg( QUERY, "float", "f" );
    QUERY->doc_func( QUERY, "convert float to integer." );

    // add getenv
    QUERY->add_sfun( QUERY, getenv_impl, "string", "getenv" ); //! fetch environment variable
    QUERY->add_arg( QUERY, "string", "key" );
    QUERY->doc_func( QUERY, "get the value of an environment variable (e.g., PATH)." );

    QUERY->add_sfun( QUERY, getenv2_impl, "string", "getenv" );//! fetch environment variable with default
    QUERY->add_arg( QUERY, "string", "key" );
    QUERY->add_arg( QUERY, "string", "default" );
    QUERY->doc_func( QUERY, "get the value of an environment variable, returning the provided default if unset." );

    // add setenv
    QUERY->add_sfun( QUERY, setenv_impl, "int", "setenv" ); //! set environment variable
    QUERY->add_arg( QUERY, "string", "key" );
    QUERY->add_arg( QUERY, "string", "value" );
    QUERY->doc_func( QUERY, "set the value of environment variable named 'key'" );

    // add mtof
    //! see \example mand-o-matic.ck
    QUERY->add_sfun( QUERY, mtof_impl, "float", "mtof" ); //! midi note to frequency
    QUERY->add_arg( QUERY, "float", "value" );
    QUERY->doc_func( QUERY, "Convert a MIDI note number to frequency (Hz). Note the input value is of type float (supports fractional note number). For reference, MIDI note number 60 is Middle C; each whole number is one semitone." );

    // add ftom
    QUERY->add_sfun( QUERY, ftom_impl, "float", "ftom" ); //! frequency to midi note
    QUERY->add_arg( QUERY, "float", "value" );
    QUERY->doc_func( QUERY, "Convert frequency (Hz) to MIDI note number space." );

    // add powtodb
    QUERY->add_sfun( QUERY, powtodb_impl, "float", "powtodb" ); //! linear power to decibel
    QUERY->add_arg( QUERY, "float", "value" );
    QUERY->doc_func( QUERY, "Convert signal power ratio to decibels (dB)." );

    // add rmstodb
    QUERY->add_sfun( QUERY, rmstodb_impl, "float", "rmstodb" ); //! rms to decibel
    QUERY->add_arg( QUERY, "float", "value" );
    QUERY->doc_func( QUERY, "Convert rms to decibels (dB)." );

    // add dbtopow
    QUERY->add_sfun( QUERY, dbtopow_impl, "float", "dbtopow" ); //! decibel to linear
    QUERY->add_arg( QUERY, "float", "value" );
    QUERY->doc_func( QUERY, "Convert decibels (dB) to signal power ratio." );

    // add dbtorms
    QUERY->add_sfun( QUERY, dbtorms_impl, "float", "dbtorms" ); //! decibel to rms
    QUERY->add_arg( QUERY, "float", "value" );
    QUERY->doc_func( QUERY, "Convert decibels (dB) to rms." );

    // add dbtolin
    QUERY->add_sfun( QUERY, dbtolin_impl, "float", "dbtolin" ); //! decibel to linear
    QUERY->add_arg( QUERY, "float", "value" );
    QUERY->doc_func( QUERY, "Convert decibels (dB) to linear amplitude." );

    // add lintodb
    QUERY->add_sfun( QUERY, lintodb_impl, "float", "lintodb" ); //! linear to decibel
    QUERY->add_arg( QUERY, "float", "value" );
    QUERY->doc_func( QUERY, "Convert linear amplitude to decibels (dB).");

    // add clamp
    QUERY->add_sfun( QUERY, clamp_impl, "int", "clamp" ); //! clamp to range (int)
    QUERY->add_arg( QUERY, "int", "value" );
    QUERY->add_arg( QUERY, "int", "min" );
    QUERY->add_arg( QUERY, "int", "max" );
    QUERY->doc_func( QUERY, "Clamp integer to range [min, max]." );

    // add clampf
    QUERY->add_sfun( QUERY, clampf_impl, "float", "clampf" ); //! clamp to range (float)
    QUERY->add_arg( QUERY, "float", "value" );
    QUERY->add_arg( QUERY, "float", "min" );
    QUERY->add_arg( QUERY, "float", "max" );
    QUERY->doc_func( QUERY, "Clamp float to range [min, max]." );

    // add scalef
    QUERY->add_sfun( QUERY, scalef_impl, "float", "scalef" ); //! scale from source range to dest range (float)
    QUERY->add_arg( QUERY, "float", "value" );
    QUERY->add_arg( QUERY, "float", "srcmin" );
    QUERY->add_arg( QUERY, "float", "srcmax" );
    QUERY->add_arg( QUERY, "float", "dstmin" );
    QUERY->add_arg( QUERY, "float", "dstmax" );
    QUERY->doc_func( QUERY, "Scale a float from source range to destination range." );

    // add range functions | 1.5.1.1 (nshaheed)
    QUERY->add_sfun(QUERY, range1_impl, "int[]", "range"); //! fetch environment variable
    QUERY->add_arg(QUERY, "int", "stop");
    QUERY->doc_func(QUERY, "Return array containing the range [0,stop).");

    QUERY->add_sfun(QUERY, range2_impl, "int[]", "range"); //! fetch environment variable
    QUERY->add_arg(QUERY, "int", "start");
    QUERY->add_arg(QUERY, "int", "stop");
    QUERY->doc_func(QUERY, "Return array containing the range [start,stop).");

    QUERY->add_sfun(QUERY, range3_impl, "int[]", "range"); //! fetch environment variable
    QUERY->add_arg(QUERY, "int", "start");
    QUERY->add_arg(QUERY, "int", "stop");
    QUERY->add_arg(QUERY, "int", "step");
    QUERY->doc_func(QUERY, "Return array containing values from start up to (but not including) stop, hopping by step.");

    // finish class
    QUERY->end_class( QUERY );

    // seed rand()
    // (NOTE: ck_srandom(time(NULL)) is invoked in ulib_math.cpp)
    // srand( (unsigned)time( NULL ) );

    Chuck_DL_Func * func = NULL;

#ifndef __DISABLE_KBHIT__
    // doc string
    string doc = "KBHit (terminal only) is a simple mechanism for capturing keyboard input; for a more flexible mechanism, see HID. (On Linux, KBHit does not require granting device permissions; it works out of the box.)";

    // begin class (KBHit)
    if( !type_engine_import_class_begin( env, "KBHit", "Event",
                                         env->global(), KBHit_ctor,
                                         KBHit_dtor, doc.c_str() ) )
        return FALSE;

    // add examples
    if( !type_engine_import_add_ex( env, "hid/kbhit/kbhit.ck" ) ) goto error;
    if( !type_engine_import_add_ex( env, "hid/kbhit/kbhit2.ck" ) ) goto error;
    if( !type_engine_import_add_ex( env, "hid/kbhit/clix.ck" ) ) goto error;
    if( !type_engine_import_add_ex( env, "hid/kbhit/clix2.ck" ) ) goto error;
    if( !type_engine_import_add_ex( env, "hid/kbhit/clix3.ck" ) ) goto error;

    // add member variable
    KBHit_offset_data = type_engine_import_mvar( env, "int", "@KBHit_data", FALSE );
    if( KBHit_offset_data == CK_INVALID_OFFSET ) goto error;

    // add on()
    func = make_new_mfun( "void", "on", KBHit_on );
    func->doc = "Enable the KBHit.";
    if( !type_engine_import_mfun( env, func ) ) goto error;

    // add off()
    func = make_new_mfun( "void", "off", KBHit_off );
    func->doc = "Disable the KBHit.";
    if( !type_engine_import_mfun( env, func ) ) goto error;

    // add state()
    func = make_new_mfun( "void", "state", KBHit_state );
    func->doc = "Get whether the KBHit is currently enabled.";
    if( !type_engine_import_mfun( env, func ) ) goto error;

    // add hit()
    func = make_new_mfun( "Event", "hit", KBHit_hit );
    func->doc = "Return itself as an Event to wait on; this is largely unnecessary as the KBHit instance can be directly => to 'now'.";
    if( !type_engine_import_mfun( env, func ) ) goto error;

    // add more()
    func = make_new_mfun( "int", "more", KBHit_more );
    func->doc = "Return whether there are unprocessed KBHit events (e.g., if a user presses multiple keys at once).";
    if( !type_engine_import_mfun( env, func ) ) goto error;

    // add getchar()
    func = make_new_mfun( "int", "getchar", KBHit_getchar );
    func->doc = "Get the ASCII value of the last keyboard press.";
    if( !type_engine_import_mfun( env, func ) ) goto error;

    // add can_wait()
    func = make_new_mfun( "int", "can_wait", KBHit_can_wait );
    func->doc = "(internal) used by virtual machine for synthronization.";
    if( !type_engine_import_mfun( env, func ) ) goto error;

    // end class
    type_engine_import_class_end( env );

    // start it
    KBHitManager::init();
#endif // __DISABLE_KBHIT__


    // register deprecate
    type_engine_register_deprecate( env, "Skot", "ConsoleInput" );

#ifndef __DISABLE_PROMPTER__
    // begin class (Skot)
    if( !type_engine_import_class_begin( env, "ConsoleInput", "Event",
                                         env->global(), Skot_ctor, Skot_dtor,
                                         "(Terminal only) a utility for prompting user input on the command line." ) )
        return FALSE;

    // add member variable
    Skot_offset_data = type_engine_import_mvar( env, "int", "@Skot_data", FALSE );
    if( Skot_offset_data == CK_INVALID_OFFSET ) goto error;

    // add prompt()
    func = make_new_mfun( "Event", "prompt", Skot_prompt );
    func->doc = "Return an Event to wait on.";
    if( !type_engine_import_mfun( env, func ) ) goto error;

    // add prompt()
    func = make_new_mfun( "Event", "prompt", Skot_prompt2 );
    func->add_arg( "string", "what" );
    func->doc = "Print a prompt text and return an Event to wait on.";
    if( !type_engine_import_mfun( env, func ) ) goto error;

    // add more()
    func = make_new_mfun( "int", "more", Skot_more );
    func->doc = "Return whether there is more input to read.";
    if( !type_engine_import_mfun( env, func ) ) goto error;

    // add getString()
    func = make_new_mfun( "string", "getLine", Skot_getLine );
    func->doc = "Return the next line of input as a string.";
    if( !type_engine_import_mfun( env, func ) ) goto error;

    // add can_wait()
    func = make_new_mfun( "int", "can_wait", Skot_can_wait );
    func->doc = "(internal) used by virtual machine for synthronization.";
    if( !type_engine_import_mfun( env, func ) ) goto error;

    // add examples
    if( !type_engine_import_add_ex( env, "string/readline.ck" ) ) goto error;
    if( !type_engine_import_add_ex( env, "ai/word2vec/word2vec-prompt.ck" ) ) goto error;
    if( !type_engine_import_add_ex( env, "ai/word2vec/poem-ungenerate.ck" ) ) goto error;

    // end class
    type_engine_import_class_end( env );
#endif // __DISABLE_PROMPTER__


    // register deprecate
    type_engine_register_deprecate( env, "PRC", "StringTokenizer" );

    // begin class (StrTok)
    if( !type_engine_import_class_begin( env, "StringTokenizer", "Object",
                                         env->global(), StrTok_ctor, StrTok_dtor,
                                         "Break a string into tokens. This uses whitespace as the delimiter." ) )
        return FALSE;

    // add member variable
    StrTok_offset_data = type_engine_import_mvar( env, "int", "@StrTok_data", FALSE );
    if( StrTok_offset_data == CK_INVALID_OFFSET ) goto error;

    // add set()
    func = make_new_mfun( "void", "set", StrTok_set );
    func->add_arg( "string", "line" );
    func->doc = "Set the string to be tokenized.";
    if( !type_engine_import_mfun( env, func ) ) goto error;

    // add reset()
    func = make_new_mfun( "void", "reset", StrTok_reset );
    func->doc = "Reset token iteration back to the beginning of the set string.";
    if( !type_engine_import_mfun( env, func ) ) goto error;

    // add more()
    func = make_new_mfun( "int", "more", StrTok_more );
    func->doc = "Return true (1) if there are still more tokens, false (0) if no more tokens.";
    if( !type_engine_import_mfun( env, func ) ) goto error;

    // add next()
    func = make_new_mfun( "string", "next", StrTok_next );
    func->doc = "Return the next token string.";
    if( !type_engine_import_mfun( env, func ) ) goto error;

    // add get()
    func = make_new_mfun( "string", "next", StrTok_next2 );
    func->add_arg( "string", "out" );
    func->doc = "Return the next token string. Additionally, write the token string to the 'out' string variable.";
    if( !type_engine_import_mfun( env, func ) ) goto error;

    // add get()
    func = make_new_mfun( "string", "get", StrTok_get );
    func->add_arg( "int", "index" );
    func->doc = "Return the i-th token in the set string.";
    if( !type_engine_import_mfun( env, func ) ) goto error;

    // add get()
    func = make_new_mfun( "string", "get", StrTok_get2 );
    func->add_arg( "int", "index" );
    func->add_arg( "string", "out" );
    func->doc = "Return the i-th token in the set string. Additionally, write the token string to the `out` string variable.";
    if( !type_engine_import_mfun( env, func ) ) goto error;

    // add size()
    func = make_new_mfun( "int", "size", StrTok_size );
    func->doc = "Returns the number of token strings that the set string can be broken into.";
    if( !type_engine_import_mfun( env, func ) ) goto error;

    // add examples
    if( !type_engine_import_add_ex( env, "string/token.ck" ) ) goto error;
    if( !type_engine_import_add_ex( env, "string/readline.ck" ) ) goto error;
    if( !type_engine_import_add_ex( env, "io/read-tokens.ck" ) ) goto error;
    if( !type_engine_import_add_ex( env, "io/jabberwocky.txt" ) ) goto error;
    if( !type_engine_import_add_ex( env, "ai/word2vec/word2vec-prompt.ck" ) ) goto error;

    // end class
    type_engine_import_class_end( env );


#ifdef AJAY

    // begin class
    // init base class
    if( !type_engine_import_class_begin( env, "VCR", "Object",
                                         env->global(), VCR_ctor ) )
        return FALSE;

    // add member variable
    VCR_offset_data = type_engine_import_mvar( env, "int", "@me", FALSE );
    if( VCR_offset_data == CK_INVALID_OFFSET ) goto error;

    // add load()
    func = make_new_mfun( "int", "load", VCR_load );
    func->add_arg( "string", "filename" );
    func->add_arg( "int", "column" );
    if( !type_engine_import_mfun( env, func ) ) goto error;

    // add reset()
    func = make_new_mfun( "int", "reset", VCR_reset );
    if( !type_engine_import_mfun( env, func ) ) goto error;

    // add seek()
    func = make_new_mfun( "int", "seek", VCR_seek );
    func->add_arg( "int", "where" );
    if( !type_engine_import_mfun( env, func ) ) goto error;

    // add more()
    func = make_new_mfun( "int", "more", VCR_more );
    if( !type_engine_import_mfun( env, func ) ) goto error;

    // add curr()
    func = make_new_mfun( "float", "curr", VCR_curr );
    if( !type_engine_import_mfun( env, func ) ) goto error;

    // add next()
    func = make_new_mfun( "int", "next", VCR_next );
    if( !type_engine_import_mfun( env, func ) ) goto error;

    // add pos()
    func = make_new_mfun( "int", "pos", VCR_pos );
    if( !type_engine_import_mfun( env, func ) ) goto error;

    // add size()
    func = make_new_mfun( "int", "size", VCR_size );
    if( !type_engine_import_mfun( env, func ) ) goto error;

    // add name()
    func = make_new_mfun( "string", "name", VCR_name );
    if( !type_engine_import_mfun( env, func ) ) goto error;

    // end the class import
    type_engine_import_class_end( env );


#if defined(__PLATFORM_WINDOWS__)
    // begin class (Cereal)
    if( !type_engine_import_class_begin( env, "Cereal", "Object",
                                         env->global(), Cereal_ctor ) )
        return FALSE;

    // add member
    Cereal_offset_data = type_engine_import_mvar( env, "int", "@Cereal_data", FALSE );
    if( Cereal_offset_data == CK_INVALID_OFFSET ) goto error;

    // add open()
    func = make_new_mfun( "int", "open", Cereal_open );
    func->add_arg( "string", "name" );
    func->add_arg( "int", "baudrate" );
    if( !type_engine_import_mfun( env, func ) ) goto error;

    // add close()
    func = make_new_mfun( "void", "close", Cereal_close );
    if( !type_engine_import_mfun( env, func ) ) goto error;

    // add more()
    func = make_new_mfun( "int", "more", Cereal_more );
    if( !type_engine_import_mfun( env, func ) ) goto error;

    // add send()
    func = make_new_mfun( "int", "send", Cereal_send );
    func->add_arg( "int", "bite" );
    if( !type_engine_import_mfun( env, func ) ) goto error;

    // add recv()
    func = make_new_mfun( "int", "recv", Cereal_recv );
    if( !type_engine_import_mfun( env, func ) ) goto error;

    // end class
    type_engine_import_class_end( env );
#endif

    return TRUE;

#else

    return TRUE;

#endif

error:

    // end the class import
    type_engine_import_class_end( env );

    return FALSE;
}

// abs
CK_DLL_SFUN( abs_impl )
{
    t_CKINT v = GET_CK_INT(ARGS);
    RETURN->v_int = v >= 0 ? v : -v;
}

// fabs
CK_DLL_SFUN( fabs_impl )
{
    t_CKFLOAT v = GET_CK_FLOAT(ARGS);
    RETURN->v_float = v >= 0.0 ? v : -v;
    //RETURN->v_float = ::fabs( GET_CK_FLOAT(ARGS) );
}

// rand
CK_DLL_SFUN( rand_impl )
{
    // 1.5.0.1 (ge) use ck_random()
    RETURN->v_int = ck_random();
}

// randf
CK_DLL_SFUN( randf_impl )
{
    // 1.5.0.4 (ge) use ck_random_f()
    RETURN->v_float = ( 2.0 * ck_random_f() - 1.0 );
}

// randf
CK_DLL_SFUN( rand2f_impl )
{
    t_CKFLOAT min = GET_CK_FLOAT(ARGS), max = *((t_CKFLOAT *)ARGS + 1);
    // 1.5.0.4 (ge) use ck_random_f()
    RETURN->v_float = min + (max-min)*::ck_random_f();
}

// randi
CK_DLL_SFUN( rand2_impl ) // inclusive.
{
    // 1.3.1.0: converted int to t_CKINT for 64-bit compatibility
    t_CKINT min = *(t_CKINT *)ARGS, max = *((t_CKINT *)ARGS + 1);
    t_CKINT range = max - min;
    if ( range == 0 )
    {
        RETURN->v_int = min;
    }
    else
    {
        if( range > 0 )
        {
            RETURN->v_int = min + (t_CKINT)( (1.0 + range) * ( ::ck_random()/(CK_RANDOM_MAX+1.0) ) );
        }
        else
        {
            RETURN->v_int = min - (t_CKINT)( (-range + 1.0) * ( ::ck_random()/(CK_RANDOM_MAX+1.0) ) );
        }
    }
}

// srand
CK_DLL_SFUN( srand_impl )
{
    t_CKINT seed = GET_CK_INT(ARGS);
    // 1.5.0.1 (ge) updated to ck_srandom()
    ck_srandom( (unsigned)seed );
}

// sgn
CK_DLL_SFUN( sgn_impl )
{
    t_CKFLOAT v = GET_CK_FLOAT(ARGS);
    RETURN->v_float = v == 0.0f ? 0.0f : ( v > 0.0f ? 1.0f : -1.0f );
}

// system
CK_DLL_SFUN( system_impl )
{
    const char * cmd = GET_CK_STRING(ARGS)->str().c_str();

#ifndef __CHIP_MODE__
    // check globals for permission
    if( !ChucK::enableSystemCall )
    {
        CK_FPRINTF_STDERR( "[chuck]:error: VM not authorized to call Std.system( string )...\n" );
        CK_FPRINTF_STDERR( "[chuck]:  (command string was: \"%s\")\n", cmd );
        CK_FPRINTF_STDERR( "[chuck]:  (note: enable via --caution-to-the-wind flag or other means)\n" );
        RETURN->v_int = 0;
    }
    else
    {
        // log
        EM_log( CK_LOG_HERALD, "invoking system( CMD )..." );
        EM_pushlog();
        EM_log( CK_LOG_HERALD, "CMD: \"%s\"", cmd );
        EM_poplog();
        RETURN->v_int = system( cmd );
    }
#else
    CK_FPRINTF_STDERR( "[chuck]:error: VM not authorized to call Std.system( string )...\n" );
    CK_FPRINTF_STDERR( "[chuck]:  (command string was: \"%s\")\n", cmd );
    CK_FPRINTF_STDERR( "[chuck]:  (note: Std.system() disabled on iOS)\n" );
    RETURN->v_int = 0;
#endif
}

// aoti
CK_DLL_SFUN( atoi_impl )
{
    Chuck_String * str = GET_CK_STRING(ARGS);
    if( str )
    {
        const char * v = str->str().c_str();
        RETURN->v_int = atoi( v );
    }
    else
    {
        RETURN->v_int = 0;
    }
}

// atof
CK_DLL_SFUN( atof_impl )
{
    Chuck_String * str = GET_CK_STRING(ARGS);
    if( str )
    {
        const char * v = GET_CK_STRING(ARGS)->str().c_str();
        RETURN->v_float = atof( v );
    }
    else
    {
        RETURN->v_float = 0.0;
    }
}

// itoa
CK_DLL_SFUN( itoa_impl )
{
    t_CKINT i = GET_CK_INT(ARGS);
    // TODO: memory leak, please fix.  Thanks.
    Chuck_String * a = (Chuck_String *)instantiate_and_initialize_object( SHRED->vm_ref->env()->ckt_string, SHRED );
    a->set( ck_itoa( i ) );
    RETURN->v_string = a;
}

// ftoa
CK_DLL_SFUN( ftoa_impl )
{
    t_CKFLOAT f = GET_NEXT_FLOAT(ARGS);
    t_CKINT p = GET_NEXT_INT(ARGS);
    Chuck_String * a = (Chuck_String *)instantiate_and_initialize_object( SHRED->vm_ref->env()->ckt_string, SHRED );
    a->set( ck_ftoa( f, (t_CKUINT)p ) );
    RETURN->v_string = a;
}

// ftoi
CK_DLL_SFUN( ftoi_impl )
{
    t_CKFLOAT f = GET_NEXT_FLOAT(ARGS);
    RETURN->v_int = (t_CKINT) f;
}

// getenv
CK_DLL_SFUN( getenv_impl )
{
    // get chuck string key
    Chuck_String * ckstr_key = GET_NEXT_STRING(ARGS);
    // check for null
    if( !ckstr_key )
    {
        // instantiate
        Chuck_String * empty = (Chuck_String *)instantiate_and_initialize_object( SHRED->vm_ref->env()->ckt_string, SHRED );
        // return value
        RETURN->v_string = empty;
        // return out of this function
        return;
    }

    const char * v = ckstr_key->str().c_str();
    const char * s = getenv( v );
    Chuck_String * a = (Chuck_String *)instantiate_and_initialize_object( SHRED->vm_ref->env()->ckt_string, SHRED );
    a->set( s ? s : "" );
    RETURN->v_string = a;
}

// getenv with default value fallback | 1.5.0.8 (@ynohtna) added
CK_DLL_SFUN( getenv2_impl )
{
    // get chuck string key and default
    Chuck_String * ckstr_key = GET_NEXT_STRING(ARGS);
    Chuck_String * ckstr_default = GET_NEXT_STRING(ARGS);
    // check for null
    if( !ckstr_key )
    {
        // return default
        RETURN->v_string = ckstr_default;
        // return out of this function
        return;
    }

    // get key as c str
    const char * k = ckstr_key->str().c_str();
    // get env value
    const char * v = getenv( k );
    if( v ) {
        Chuck_String * a = (Chuck_String *)instantiate_and_initialize_object( SHRED->vm_ref->env()->ckt_string, SHRED );
        a->set( v ? v : "" );
        RETURN->v_string = a;
    } else {
        Chuck_String * d = ckstr_default;
        RETURN->v_string = d;
    }
}

// setenv
CK_DLL_SFUN( setenv_impl )
{
    const char * v1 = GET_NEXT_STRING(ARGS)->str().c_str();
    const char * v2 = GET_NEXT_STRING(ARGS)->str().c_str();
    RETURN->v_int = setenv( v1, v2, 1 );
}

// mtof
CK_DLL_SFUN( mtof_impl )
{
    t_CKFLOAT v = GET_CK_FLOAT(ARGS);
    RETURN->v_float = ck_mtof(v);
}

// ftom
CK_DLL_SFUN( ftom_impl )
{
    t_CKFLOAT v = GET_CK_FLOAT(ARGS);
    RETURN->v_float = ck_ftom(v);
}

// powtodb
CK_DLL_SFUN( powtodb_impl )
{
    t_CKFLOAT v = GET_CK_FLOAT(ARGS);
    RETURN->v_float = ck_powtodb(v);
}

// rmstodb
CK_DLL_SFUN( rmstodb_impl )
{
    t_CKFLOAT v = GET_CK_FLOAT(ARGS);
    RETURN->v_float = ck_rmstodb(v);
}

// dbtopow
CK_DLL_SFUN( dbtopow_impl )
{
    t_CKFLOAT v = GET_CK_FLOAT(ARGS);
    RETURN->v_float = ck_dbtopow(v);
}

// dbtorms
CK_DLL_SFUN( dbtorms_impl )
{
    t_CKFLOAT v = GET_CK_FLOAT(ARGS);
    RETURN->v_float = ck_dbtorms(v);
}

CK_DLL_SFUN( dbtolin_impl )
{
    t_CKFLOAT v = GET_CK_FLOAT(ARGS);
    RETURN->v_float = pow(10.0, v/20.0);
}

CK_DLL_SFUN( lintodb_impl )
{
    t_CKFLOAT v = GET_CK_FLOAT(ARGS);
    RETURN->v_float = 20.0*log10(v);
}

CK_DLL_SFUN( clamp_impl )
{
    t_CKINT v = GET_NEXT_INT(ARGS);
    t_CKINT min = GET_NEXT_INT(ARGS);
    t_CKINT max = GET_NEXT_INT(ARGS);

    if(v < min) RETURN->v_int = min;
    else if(v > max) RETURN->v_int = max;
    else RETURN->v_int = v;
}

CK_DLL_SFUN( clampf_impl )
{
    t_CKFLOAT v = GET_NEXT_FLOAT(ARGS);
    t_CKFLOAT min = GET_NEXT_FLOAT(ARGS);
    t_CKFLOAT max = GET_NEXT_FLOAT(ARGS);

    if(v < min) RETURN->v_float = min;
    else if( v > max) RETURN->v_float = max;
    else RETURN->v_float = v;
}

CK_DLL_SFUN( scalef_impl )
{
    t_CKFLOAT v = GET_NEXT_FLOAT(ARGS);
    t_CKFLOAT srcmin = GET_NEXT_FLOAT(ARGS);
    t_CKFLOAT srcmax = GET_NEXT_FLOAT(ARGS);
    t_CKFLOAT dstmin = GET_NEXT_FLOAT(ARGS);
    t_CKFLOAT dstmax = GET_NEXT_FLOAT(ARGS);

    RETURN->v_float = dstmin + (dstmax-dstmin) * ((v-srcmin)/(srcmax-srcmin));
}

// common internal function for range() | 1.5.1.1 (nshaheed)
static Chuck_ArrayInt * ck_range( t_CKINT start, t_CKINT stop, t_CKINT step, Chuck_VM_Shred * SHRED )
{
    // size
    t_CKINT size = 0;
    // calculate size based on comparing start and stop
    if( start < stop && step > 0 ) size = (stop-start)/step + ((stop-start)%step ? 1 : 0);
    else if( start > stop && step < 0 ) size = (start-stop)/(-step) + ((start-stop)%(-step) ? 1 : 0);

    // allocate array object
    Chuck_ArrayInt * range = new Chuck_ArrayInt(FALSE, size);
    // initialize with trappings of Object
    initialize_object(range, SHRED->vm_ref->env()->ckt_array, SHRED, SHRED->vm_ref);

    // the value
    t_CKINT value = start;
    // populate the array
    for (t_CKINT i = 0; i < size; i++) {
        range->set(i, value);
        value += step;
    }

    // return the array reference
    return range;
}

// basic range function - make array with values from [0,stop)
CK_DLL_SFUN(range1_impl)
{
    t_CKINT start = 0;
    t_CKINT stop = GET_NEXT_INT(ARGS);
    t_CKINT step = stop > start ? 1 : -1;
    RETURN->v_object = ck_range( start, stop, step, SHRED );
}

// start/stop range function - make array with values from [start,stop)
CK_DLL_SFUN(range2_impl)
{
    t_CKINT start = GET_NEXT_INT(ARGS);
    t_CKINT stop = GET_NEXT_INT(ARGS);
    t_CKINT step = stop > start ? 1 : -1;
    RETURN->v_object = ck_range( start, stop, step, SHRED );
}

// start/stop/step range function - make array with values from [start,stop) with step stride
CK_DLL_SFUN(range3_impl)
{
    t_CKINT start = GET_NEXT_INT(ARGS);
    t_CKINT stop = GET_NEXT_INT(ARGS);
    t_CKINT step = GET_NEXT_INT(ARGS);

    // check and flip signs to match with stop-start
    if( (start < stop && step < 0) ||
        (start > stop && step > 0 ) ) step = -step;

    RETURN->v_object = ck_range( start, stop, step, SHRED );
}




#ifndef __DISABLE_KBHIT__
// static
CBufferAdvance * KBHitManager::the_buf = NULL;
t_CKINT KBHitManager::the_onoff = 0;
t_CKBOOL KBHitManager::the_init = FALSE;
XThread * KBHitManager::the_thread;
#define BUFFER_SIZE 1024


#if !defined(__PLATFORM_WINDOWS__) || defined(__WINDOWS_PTHREAD__)
static void * kb_loop( void * )
#else
static unsigned int __stdcall kb_loop( void * )
#endif
{
    t_CKINT c;
    // EM_log( CK_LOG_INFO, "starting kb loop..." );

    // go
    while( KBHitManager::the_init )
    {
        // if on
        if( KBHitManager::the_onoff )
        {
            // see if we have kb hit
            while( kb_hit() )
            {
                // get the next char
                c = kb_getch();
                // queue it
                KBHitManager::the_buf->put( &c, 1 );
            }
        }

        // wait
        ck_usleep( 5000 );
    }

    return 0;
}


// init
t_CKBOOL KBHitManager::init()
{
    // sanity check
    if( the_buf ) return FALSE;

    EM_log( CK_LOG_INFO, "initializing KBHitManager..." );
    the_buf = new CBufferAdvance;
    if( !the_buf->initialize( BUFFER_SIZE, sizeof(t_CKINT) ) )
    {
        EM_log( CK_LOG_HERALD, "KBHitManager: couldn't allocate central KB buffer..." );
        CK_SAFE_DELETE( the_buf );
        return FALSE;
    }

    the_onoff = 0;
    the_init = TRUE;
    the_thread = new XThread;
    EM_pushlog();
    EM_log( CK_LOG_INFO, "starting kb loop..." );
    EM_poplog();
    the_thread->start( kb_loop, NULL );

    return TRUE;
}


// shutdown
void KBHitManager::shutdown()
{
    EM_log( CK_LOG_INFO, "shutting down KBHitManager..." );
    CK_SAFE_DELETE( the_buf );
    kb_endwin();

    the_onoff = 0;
    the_init = FALSE;
}


// on()
void KBHitManager::on()
{
    the_onoff++;
    if( !kb_ready() )
        kb_initscr();
}


// off()
void KBHitManager::off()
{
    the_onoff--;
    if( kb_ready() && the_onoff == 0 )
        kb_endwin();
}


// open()
t_CKBOOL KBHitManager::open( KBHit * kb )
{
    if( the_buf == NULL ) return FALSE;
    if( kb->m_buffer != NULL ) return FALSE;

    EM_log( CK_LOG_INFO, "adding KBHit..." );
    // init the kb
    kb->m_buffer = the_buf;
    // read index
    kb->m_read_index = kb->m_buffer->join( (Chuck_Event *)kb->SELF );

    return TRUE;
}


// close()
t_CKBOOL KBHitManager::close( KBHit * kb )
{
    if( the_buf == NULL ) return FALSE;
    if( kb->m_buffer == NULL ) return FALSE;

    EM_log( CK_LOG_INFO, "removing KBHit..." );
    // unjoin
    kb->m_buffer->resign( kb->m_read_index );
    // done
    kb->m_read_index = 0;

    return TRUE;
}


// KBHit
KBHit::KBHit()
{
    m_buffer = NULL;
    m_read_index = 0;
    m_valid = FALSE;
    m_onoff = FALSE;
    m_echo = FALSE;
    SELF = NULL;
}


// ~KBHit()
KBHit::~KBHit()
{
    this->close();
}


// open()
t_CKBOOL KBHit::open()
{
    return KBHitManager::open( this );
}


// close()
t_CKBOOL KBHit::close()
{
    return KBHitManager::close( this );
}


// on()
void KBHit::on()
{
    if( m_onoff == FALSE )
    {
        KBHitManager::on();
        open();
        m_onoff = TRUE;
    }
}


// off()
void KBHit::off()
{
    if( m_onoff == TRUE )
    {
        KBHitManager::off();
        open();
        m_onoff = FALSE;
    }
}


// empty
t_CKBOOL KBHit::empty()
{
    if( m_buffer == NULL ) return TRUE;
    if( m_onoff == FALSE ) return TRUE;
    // if( m_read_index == 0 ) return TRUE;
    return m_buffer->empty( m_read_index );
}


// getch
t_CKINT KBHit::getch()
{
    t_CKINT c;
    if( empty() ) return 0;
    m_buffer->get( &c, 1, m_read_index );
    return c;
}


// ctor
CK_DLL_CTOR( KBHit_ctor )
{
    KBHit * kb = new KBHit;
    OBJ_MEMBER_INT(SELF, KBHit_offset_data) = (t_CKINT)kb;
    kb->SELF = SELF;
    kb->on();
}


// dtor
CK_DLL_DTOR( KBHit_dtor )
{
    delete (KBHit *)OBJ_MEMBER_INT(SELF, KBHit_offset_data);
    OBJ_MEMBER_INT(SELF, KBHit_offset_data) = 0;
}

// on
CK_DLL_MFUN( KBHit_on )
{
    KBHit * kb = (KBHit *)(OBJ_MEMBER_INT(SELF, KBHit_offset_data));
    kb->on();
}


// off
CK_DLL_MFUN( KBHit_off )
{
    KBHit * kb = (KBHit *)(OBJ_MEMBER_INT(SELF, KBHit_offset_data));
    kb->off();
}


// state
CK_DLL_MFUN( KBHit_state )
{
    KBHit * kb = (KBHit *)(OBJ_MEMBER_INT(SELF, KBHit_offset_data));
    RETURN->v_int = kb->state();
}


// hit
CK_DLL_MFUN( KBHit_hit )
{
    //KBHit * kb = (KBHit *)(OBJ_MEMBER_INT(SELF, KBHit_offset_data));
    RETURN->v_object = SELF;
}


// more
CK_DLL_MFUN( KBHit_more )
{
    KBHit * kb = (KBHit *)(OBJ_MEMBER_INT(SELF, KBHit_offset_data));
    RETURN->v_int = (kb->empty() == FALSE);
}


// getchar
CK_DLL_MFUN( KBHit_getchar )
{
    KBHit * kb = (KBHit *)(OBJ_MEMBER_INT(SELF, KBHit_offset_data));
    RETURN->v_int = kb->getch();
}


// can wait
CK_DLL_MFUN( KBHit_can_wait )
{
    KBHit * kb = (KBHit *)(OBJ_MEMBER_INT(SELF, KBHit_offset_data));
    RETURN->v_int = kb->empty();
}
#endif // __DISABLE_KBHIT__




#ifndef __DISABLE_PROMPTER__
class LineEvent : public Chuck_Event
{
public:
    LineEvent( Chuck_Event * SELF );
    ~LineEvent();

public:
    void prompt( const string & what = "" );
    t_CKBOOL more();
    string getLine();
    t_CKBOOL can_wait();
    void enqueue( const string & line )
    { m_q.push( line ); }

    Chuck_Event * SELF;

protected:
    queue<string> m_q;
};

// global variables
t_CKBOOL g_le_launched = FALSE;
t_CKBOOL g_le_wait = TRUE;
CHUCK_THREAD g_le_thread = 0;
map<LineEvent *, LineEvent *> g_le_map;
XMutex g_le_mutex;
string g_le_what;

// final cleanup (per host)
void le_cleanup()
{
    if( g_le_thread )
    {
        // cancel thread
#if !defined(__PLATFORM_WINDOWS__) || defined(__WINDOWS_PTHREAD__)
        pthread_cancel( g_le_thread );
#else
        CloseHandle( g_le_thread );
#endif
        g_le_thread = 0;
        // reset
        g_le_launched = FALSE;
        g_le_wait = TRUE;
    }
}

// le callback
void * le_cb( void * p )
{
    char line[2048];
    map<LineEvent *, LineEvent *>::iterator iter;
    LineEvent * le = NULL;

    // loop
    while( true )
    {
        // wait
        while( g_le_wait )
            ck_usleep( 10000 );

        // REFACTOR-2017: TODO Ge:
        // I removed the check here for if there are no more vms running
        // Theoretically, we will always just kill this thread from outside of
        // it. Does that sound right to you?

        // do the prompt
        cout << g_le_what;
        cout.flush();
        if( !cin.getline( line, 2048 ) ) break;

        // lock
        g_le_mutex.acquire();
        // go through
        for( iter = g_le_map.begin(); iter != g_le_map.end(); iter++ )
        {
            // get the line event
            le = (*iter).first;
            // add to its queue
            le->enqueue( line );
            // broadcast it
            le->SELF->queue_broadcast();
        }
        // unlock
        g_le_mutex.release();

        // reset wait
        g_le_wait = TRUE;
    }

    return NULL;
}


LineEvent::LineEvent( Chuck_Event * SELF )
{
    // launch the cb
    if( !g_le_launched )
    {
#if !defined(__PLATFORM_WINDOWS__) || defined(__WINDOWS_PTHREAD__)
        pthread_create( &g_le_thread, NULL, le_cb, NULL );
#else
        g_le_thread = CreateThread( NULL, 0, (LPTHREAD_START_ROUTINE)le_cb, NULL, 0, 0 );
#endif
        g_le_launched = TRUE;
    }

    // lock
    g_le_mutex.acquire();
    // add
    g_le_map[this] = this;
    this->SELF = SELF;
    // unlock
    g_le_mutex.release();
}

LineEvent::~LineEvent()
{
    // remove from global map
    g_le_map.erase( this );
}

void LineEvent::prompt( const string & what )
{
    // set what
    g_le_what = what;

    // remove the extra space
    // if( g_le_what != "" ) g_le_what += " ";

    // signal
    g_le_wait = FALSE;
}

t_CKBOOL LineEvent::more()
{
    // more
    return m_q.size() > 0;
}

string LineEvent::getLine()
{
    string ret;

    // lock
    g_le_mutex.acquire();
    // get next line
    if( m_q.size() )
    {
        // get it
        ret = m_q.front();
        // dequeue it
        m_q.pop();
    }
    else
    {
        ret = "[ERROR -> getLine() called on empty Skot]";
    }
    // unlock
    g_le_mutex.release();

    return ret;
}

t_CKBOOL LineEvent::can_wait()
{
    return !more();
}


// Skot
CK_DLL_CTOR( Skot_ctor )
{
    LineEvent * le = new LineEvent((Chuck_Event *)SELF);
    OBJ_MEMBER_INT(SELF, Skot_offset_data) = (t_CKINT)le;
}

CK_DLL_DTOR( Skot_dtor )
{
    delete (LineEvent *)OBJ_MEMBER_INT(SELF, Skot_offset_data);
    OBJ_MEMBER_INT(SELF, Skot_offset_data) = 0;
}

CK_DLL_MFUN( Skot_prompt )
{
    LineEvent * le = (LineEvent *)OBJ_MEMBER_INT(SELF, Skot_offset_data);
    le->prompt();
    RETURN->v_int = (t_CKINT)(SELF);
}

CK_DLL_MFUN( Skot_prompt2 )
{
    LineEvent * le = (LineEvent *)OBJ_MEMBER_INT(SELF, Skot_offset_data);
    const char * v = GET_CK_STRING(ARGS)->str().c_str();
    le->prompt( v );
    RETURN->v_int = (t_CKINT)(SELF);
}

CK_DLL_MFUN( Skot_more )
{
    LineEvent * le = (LineEvent *)OBJ_MEMBER_INT(SELF, Skot_offset_data);
    RETURN->v_int = le->more();
}

CK_DLL_MFUN( Skot_getLine )
{
    LineEvent * le = (LineEvent *)OBJ_MEMBER_INT(SELF, Skot_offset_data);
    // TODO: memory leak
    Chuck_String * a = (Chuck_String *)instantiate_and_initialize_object( SHRED->vm_ref->env()->ckt_string, SHRED );
    a->set( le->getLine() );
    RETURN->v_string = a;
}

CK_DLL_MFUN( Skot_can_wait )
{
    LineEvent * le = (LineEvent *)OBJ_MEMBER_INT(SELF, Skot_offset_data);
    RETURN->v_int = le->can_wait();
}
#endif // __DISABLE_PROMPTER__


// StrTok
class StrTok
{
public:
    StrTok();
    ~StrTok();

public:
    void set( const string & line );
    void reset();
    t_CKBOOL more();
    string next();
    string get( t_CKINT index );
    t_CKINT size();

protected:
    istringstream * m_ss;
    string m_next;
    vector<string> m_tokens;
    vector<string>::size_type m_index;
};

StrTok::StrTok()
{
    m_ss = NULL;
    m_index = 0;
}

StrTok::~StrTok()
{
    CK_SAFE_DELETE( m_ss );
}

void StrTok::set( const string & line )
{
    string s;

    // delete
    CK_SAFE_DELETE( m_ss );
    // alloc
    m_ss = new istringstream( line );
    // read
    reset();
    m_tokens.clear();
    while( (*m_ss) >> s )
        m_tokens.push_back( s );
}

void StrTok::reset()
{
    m_index = 0;
}

t_CKBOOL StrTok::more()
{
    return m_index < m_tokens.size();
}

string StrTok::next()
{
    if( !more() ) return "";
    return m_tokens[m_index++];
}

string StrTok::get( t_CKINT index )
{
    if( index >= (t_CKINT)m_tokens.size() ) return "";
    return m_tokens[index];
}

t_CKINT StrTok::size()
{
    return (t_CKINT)m_tokens.size();
}

CK_DLL_CTOR( StrTok_ctor )
{
    StrTok * tokens = new StrTok;
    OBJ_MEMBER_INT(SELF, StrTok_offset_data) = (t_CKINT)tokens;
}

CK_DLL_DTOR( StrTok_dtor )
{
    delete (StrTok *)OBJ_MEMBER_INT(SELF, StrTok_offset_data);
    OBJ_MEMBER_INT(SELF, StrTok_offset_data) = 0;
}

CK_DLL_MFUN( StrTok_set )
{
    StrTok * tokens = (StrTok *)OBJ_MEMBER_INT(SELF, StrTok_offset_data);
    Chuck_String * s = GET_CK_STRING(ARGS);
    if( s ) tokens->set( s->str() );
    else tokens->set( "" );
}

CK_DLL_MFUN( StrTok_reset )
{
    StrTok * tokens = (StrTok *)OBJ_MEMBER_INT(SELF, StrTok_offset_data);
    tokens->reset();
}

CK_DLL_MFUN( StrTok_more )
{
    StrTok * tokens = (StrTok *)OBJ_MEMBER_INT(SELF, StrTok_offset_data);
    RETURN->v_int = (t_CKINT)tokens->more();
}

CK_DLL_MFUN( StrTok_next )
{
    StrTok * tokens = (StrTok *)OBJ_MEMBER_INT(SELF, StrTok_offset_data);
    Chuck_String * a = (Chuck_String *)instantiate_and_initialize_object( SHRED->vm_ref->env()->ckt_string, SHRED );
    a->set( tokens->next() );
    RETURN->v_string = a;
}

CK_DLL_MFUN( StrTok_next2 )
{
    StrTok * tokens = (StrTok *)OBJ_MEMBER_INT(SELF, StrTok_offset_data);
    Chuck_String * a = GET_CK_STRING(ARGS);
    string s = tokens->next();
    if( a ) a->set( s );
    RETURN->v_string = a;
}

CK_DLL_MFUN( StrTok_get )
{
    StrTok * tokens = (StrTok *)OBJ_MEMBER_INT(SELF, StrTok_offset_data);
    t_CKINT index = GET_NEXT_INT(ARGS);
    Chuck_String * a = (Chuck_String *)instantiate_and_initialize_object( SHRED->vm_ref->env()->ckt_string, SHRED );
    string s = tokens->get( index );
    a->set( s );
    RETURN->v_string = a;
}

CK_DLL_MFUN( StrTok_get2 )
{
    StrTok * tokens = (StrTok *)OBJ_MEMBER_INT(SELF, StrTok_offset_data);
    t_CKINT index = GET_NEXT_INT(ARGS);
    Chuck_String * a = GET_NEXT_STRING(ARGS);
    string s = tokens->get( index );
    if( a ) a->set( s );
    RETURN->v_string = a;
}

CK_DLL_MFUN( StrTok_size )
{
    StrTok * tokens = (StrTok *)OBJ_MEMBER_INT(SELF, StrTok_offset_data);
    RETURN->v_int = tokens->size();
}




#ifdef AJAY


class ColumnReader
{
public:
    ColumnReader();
    virtual ~ColumnReader();

    bool init( const string & filename, long col );

    bool reset() { if( !fin.good() ) return false; where = 0; return true; }
    bool seek( long pos ) { if( pos < 0 || pos >= values.size() ) return false; where = pos; return true; }
    bool more() { return where < values.size(); }
    double curr();
    bool next() { if( where < values.size() ) where++; return more(); }
    long pos() { return where; }
    long size() { return values.size(); }
    string name() { return n; }

    Chuck_String s;

protected:
    bool get_str( string & out );
    bool get_double( double & out );

protected:
    ifstream fin;
    string n;
    long column;
    long where;
    char line[0x1000];
    long len;

    vector<double> values;
};


ColumnReader::ColumnReader()
{
    n = "[NONE]";
    column = -1;
    where = -1;
    len = 0x1000;
}


ColumnReader::~ColumnReader()
{
    // close file
    if( fin.good() )
        fin.close();
}

bool ColumnReader::init( const string & filename, long col )
{
    // hmm
    if( col < 1 )
    {
        CK_STDCERR << "column must be greater than 0!!!" << CK_STDENDL;
        return false;
    }

    // open file
    fin.open( filename.c_str(), ios::in );
    // yes
    if( !fin.good() )
    {
        CK_STDCERR << "ColumnReader: cannot open file: '" << filename << "'..." << CK_STDENDL;
        return false;
    }

    // set column
    column = col;

    // read first line
    if( !fin.getline( line, len ) )
    {
        CK_STDCERR << "ColumnReader: cannot read first line: '" << filename << "'..." << CK_STDENDL;
        return false;
    }

    // get the name
    if( !get_str( n ) )
    {
        CK_STDCERR << "ColumnReader: cannot seek to column " << col << ": " << filename << "..." << CK_STDENDL;
        return false;
    }

    double v;
    long i = 1;
    // read values
    while( fin.getline( line, len ) )
    {
        v = 0.0;
        // get value
        if( !get_double( v ) )
        {
            CK_STDCERR << "ColumnReader: cannot read column " << v << " on line i: " << n << "..." << CK_STDENDL;
            return false;
        }

        values.push_back( v );
    }

    // well
    if( values.size() == 0 )
    {
        CK_STDCERR << "ColumnReader: file doesn't not contain data after first line: " << n << "..." << CK_STDENDL;
        return false;
    }

    // set location
    where = 0;

    s.str = n;

    return true;
}

double ColumnReader::curr()
{
    if( where >= values.size() )
    {
        CK_STDCERR << "ColumnReader: trying to read beyond end of file: " << n << "..." << CK_STDENDL;
        return 0.0;
    }

    return values[where];
}

bool ColumnReader::get_double( double & out )
{
    assert( column > 0 );
    long c = 1;

    char * start = line;
    char * curr = start;

    while( c < column )
    {
        // move past value
        while( *curr && *curr != ',' ) curr++;
        // move past ,
        while( *curr && *curr == ',' ) curr++;
        // check
        if( *curr == '\0' )
        {
            CK_STDCERR << "ColumnReader: cannot find column " << column << ": " << n << CK_STDENDL;
            return false;
        }

        // increment
        c++;
        // set start
        start = curr;
    }

    // move past value
    while( *curr && *curr != ',' ) curr++;
    // end
    *curr = '\0';

    out = atof( start );

    return true;
}


bool ColumnReader::get_str( string & out )
{
    assert( column > 0 );
    long c = 1;

    char * start = line;
    char * curr = start;

    while( c < column )
    {
        // move past value
        while( *curr && *curr != ',' ) curr++;
        // move past ,
        while( *curr && *curr == ',' ) curr++;
        // check
        if( *curr == '\0' )
        {
            CK_STDCERR << "ColumnReader: cannot find column " << column << ": " << n << CK_STDENDL;
            return false;
        }

        // increment
        c++;
        // set start
        start = curr;
    }

    // move past value
    while( *curr && *curr != ',' ) curr++;
    // end
    *curr = '\0';

    out = start;

    return true;
}


CK_DLL_CTOR( VCR_ctor )
{
    OBJ_MEMBER_INT(SELF, VCR_offset_data) = (t_CKUINT)new ColumnReader;
}

CK_DLL_MFUN( VCR_load )
{
    ColumnReader * vcr = (ColumnReader*)OBJ_MEMBER_INT(SELF, VCR_offset_data);
    const char * filename = GET_NEXT_STRING(ARGS)->str.c_str();
    t_CKINT column = GET_NEXT_INT(ARGS);
    RETURN->v_int = vcr->init( filename, column );
}

CK_DLL_MFUN( VCR_reset )
{
    ColumnReader * vcr = (ColumnReader*)OBJ_MEMBER_INT(SELF, VCR_offset_data);
    RETURN->v_int = vcr->reset();
}

CK_DLL_MFUN( VCR_seek )
{
    ColumnReader * vcr = (ColumnReader*)OBJ_MEMBER_INT(SELF, VCR_offset_data);
    t_CKINT where = GET_CK_INT(ARGS);
    RETURN->v_int = vcr->seek( where );
}

CK_DLL_MFUN( VCR_more )
{
    ColumnReader * vcr = (ColumnReader*)OBJ_MEMBER_INT(SELF, VCR_offset_data);
    RETURN->v_int = vcr->more();
}

CK_DLL_MFUN( VCR_curr )
{
    ColumnReader * vcr = (ColumnReader*)OBJ_MEMBER_INT(SELF, VCR_offset_data);
    RETURN->v_float = vcr->curr();
}

CK_DLL_MFUN( VCR_next )
{
    ColumnReader * vcr = (ColumnReader*)OBJ_MEMBER_INT(SELF, VCR_offset_data);
    RETURN->v_int = vcr->next();
}

CK_DLL_MFUN( VCR_pos )
{
    ColumnReader * vcr = (ColumnReader*)OBJ_MEMBER_INT(SELF, VCR_offset_data);
    RETURN->v_int = vcr->pos();
}

CK_DLL_MFUN( VCR_size )
{
    ColumnReader * vcr = (ColumnReader*)OBJ_MEMBER_INT(SELF, VCR_offset_data);
    RETURN->v_int = vcr->size();
}

CK_DLL_MFUN( VCR_name )
{
    ColumnReader * vcr = (ColumnReader*)OBJ_MEMBER_INT(SELF, VCR_offset_data);
    RETURN->v_string = &(vcr->s);
}


#ifdef __PLATFORM_WINDOWS__


// jeff's cereal
class Serial
{
public:
    // one stop bit, 8 bits, no parity
    Serial();
    ~Serial();

    unsigned read( char * buffer, unsigned numberOfBytesToRead );
    unsigned write( char * buffer, unsigned numberOfBytesToWrite );

    void write( char c );
    char read();

    unsigned available() const;

    t_CKBOOL open( char * port, t_CKUINT baudrate );
    void close();

private:
    HANDLE serialFile;
};


Serial::Serial( )
{
    serialFile = NULL;
}

Serial::~Serial( )
{
    close();
}

t_CKBOOL Serial::open( char * port, t_CKUINT baudrate )
{
    if( serialFile )
        close();

    // open port
    serialFile = CreateFile( port,
                             GENERIC_READ | GENERIC_WRITE,
                             0,
                             0,
                             OPEN_EXISTING,
                             FILE_ATTRIBUTE_NORMAL,
                             0 );
    if( serialFile == INVALID_HANDLE_VALUE )
    {
        if( GetLastError() == ERROR_FILE_NOT_FOUND )
        {
            EM_log( CK_LOG_SYSTEM, "error: cannot open serial port '%s'...", port );
        }
        else
        {
            EM_log( CK_LOG_SYSTEM, "error opening serial port '%s'...", port );
        }

        return FALSE;
    }

    // set params
    DCB dcbSerialParams = {0};
    dcbSerialParams.DCBlength = sizeof( dcbSerialParams );
    if( !GetCommState( serialFile, &dcbSerialParams) )
    {
        EM_log( CK_LOG_SYSTEM, "error getting serial state..." );
        close();
        return FALSE;
    }

    dcbSerialParams.BaudRate = baudrate;
    dcbSerialParams.ByteSize = 8;
    dcbSerialParams.StopBits = ONESTOPBIT;
    dcbSerialParams.Parity = NOPARITY;
    if( !SetCommState( serialFile, &dcbSerialParams ) )
    {
        EM_log( CK_LOG_SYSTEM, "error setting serial state..." );
        close();
        return FALSE;
    }

    // SET TIMEOUTS
    /*
    COMMTIMEOUTS timeouts = {0};
    timeouts.ReadIntervalTimeout = 50;
    timeouts.ReadTotalTimeoutConstant = 50;
    timeouts.ReadTotalTimeoutMultiplier = 10;
    timeouts.WriteTotalTimeoutConstant= 50;
    timeouts.WriteTotalTimeoutMultiplier = 10;
    if( !SetCommTimeouts( serialFile, &timeouts ) )
    {
        //error occureed. Inform user
    }
    */

    return TRUE;
}

void Serial::close()
{
    if( serialFile )
    {
        CloseHandle( serialFile );
        serialFile = NULL;
    }
}

void Serial::write( char c )
{
    write( &c, 1 );
}

char Serial::read()
{
    char c = '\0';
    read( &c, 1 );
    return c;
}

unsigned Serial::available() const
{
    struct _COMSTAT status;
    unsigned long etat;
    memset( &status, 0, sizeof(status) );

    if( serialFile )
    {
        ClearCommError( serialFile, &etat, &status);
        return status.cbInQue;
    }

    return 0;
}

// try to read numberOfBytesToRead into buffer, return how many bytes read
unsigned Serial::read( char * buffer, unsigned numberOfBytesToRead )
{
    DWORD bytesRead = 0;
    if( serialFile )
        ReadFile( serialFile, buffer, numberOfBytesToRead, &bytesRead, NULL );
    return bytesRead;
}

// try to write numberOfBytesToWrite to serial from buffer, return how many bytes written
unsigned Serial::write( char * buffer, unsigned numberOfBytesToWrite )
{
    DWORD bytesWritten = 0;
    if( serialFile )
        WriteFile( serialFile, buffer, numberOfBytesToWrite, &bytesWritten, NULL );
    return bytesWritten;
}


// ctor
CK_DLL_CTOR( Cereal_ctor )
{
    OBJ_MEMBER_UINT(SELF, Cereal_offset_data) = (t_CKUINT)new Serial;
}

// open
CK_DLL_MFUN( Cereal_open )
{
    Serial * s = (Serial *)OBJ_MEMBER_UINT(SELF, Cereal_offset_data);
    Chuck_String * str = GET_NEXT_STRING(ARGS);
    t_CKINT i = GET_NEXT_INT(ARGS);

    // close
    s->close();

    if( str )
        RETURN->v_int = s->open( (char *)str->str.c_str(), i );
    else
        RETURN->v_int = 0;
}

// close
CK_DLL_MFUN( Cereal_close )
{
    Serial * s = (Serial *)OBJ_MEMBER_UINT(SELF, Cereal_offset_data);
    s->close();
}

// more
CK_DLL_MFUN( Cereal_more )
{
    Serial * s = (Serial *)OBJ_MEMBER_UINT(SELF, Cereal_offset_data);
    RETURN->v_int = s->available();
}

// send
CK_DLL_MFUN( Cereal_send )
{
    Serial * s = (Serial *)OBJ_MEMBER_UINT(SELF, Cereal_offset_data);
    t_CKINT i = GET_NEXT_INT(ARGS);
    s->write( i );
    RETURN->v_int = i;
}

// recv
CK_DLL_MFUN( Cereal_recv )
{
    Serial * s = (Serial *)OBJ_MEMBER_UINT(SELF, Cereal_offset_data);
    if( s->available() )
        RETURN->v_int = (t_CKINT)s->read();
    else
        RETURN->v_int = 0;
}

#endif


#endif
