//////////////////////////////////////////////////////////////////////
// Simple security system.
// By Antony Fairport.
//
// Revision history.
// =================
//
// 2011-06-27
// Initial version.

//////////////////////////////////////////////////////////////////////
// Constants.
string CONFIG_FILE = "Security";

//////////////////////////////////////////////////////////////////////
// Global configuration.
float g_nRange;         // Range to scan.
float g_nFrequency;     // Frequency of checks.
list  g_lOwners;        // Owner list.
list  g_lAccess;        // Access list.

//////////////////////////////////////////////////////////////////////
// Config reading globals.
key     g_keyConfig;
integer g_iConfig;

//////////////////////////////////////////////////////////////////////
// Is the given avatar on the given list?
integer AvatarInList( string sAvatar, list l )
{
    return ~llListFindList( l, [ sAvatar ] );
}

//////////////////////////////////////////////////////////////////////
// Is the given avatar an owner?
integer IsOwner( string sName )
{
    return AvatarInList( sName, g_lOwners );
}

//////////////////////////////////////////////////////////////////////
// Does the given avatar have access?
integer HasAccess( string sName )
{
    return AvatarInList( sName, g_lAccess );
}

//////////////////////////////////////////////////////////////////////
// Eject the intruder.
Eject( key keyIntruder )
{
    // Tell the intruder what we're doing to them.
    llInstantMessage( keyIntruder, "You do not have access to this location. Sending you home." );
    
    // And send them home.
    llTeleportAgentHome( keyIntruder );
}

//////////////////////////////////////////////////////////////////////
// Default state.
default
{
    //////////////////////////////////////////////////////////////////
    // State entry.
    state_entry()
    {
        // Read the configuration.
        state Configure;
    }
}

//////////////////////////////////////////////////////////////////////
// Configuration state.
state Configure
{
    //////////////////////////////////////////////////////////////////
    // State entry.
    state_entry()
    {
        // Let the user know what's happening.
        llWhisper( 0, "Reading configuration." );
        
        // Clear out the config.
        g_nRange     = 96.0;
        g_nFrequency = 5.0;
        g_lOwners    = [];
        g_lAccess    = [];
        
        // If it looks like we've got config...
        if ( llGetInventoryType( CONFIG_FILE ) == INVENTORY_NOTECARD )
        {
            // ...start reading it.
            g_keyConfig = llGetNotecardLine( CONFIG_FILE, g_iConfig = 0 );
        }
        else
        {
            // Tell the user things aren't good.
            llWhisper( 0, "No security configuration found. Turning off." );
        }
    }
    
    //////////////////////////////////////////////////////////////////
    // Handle data server responses.
    dataserver( key queryid, string data )
    {
        // If this is our query...
        if ( queryid == g_keyConfig )
        {
            // If this isn't the end of the file...
            if ( data != EOF )
            {
                // If the line doesn't look like it's a comment, and it isn't empty...
                if ( ( llGetSubString( data, 0, 0 ) != "#" ) && ( llStringTrim( data, STRING_TRIM ) != "" ) )
                {
                    // Split the line into a list.
                    list sLineData = llParseString2List( data, [ "=" ], [] );
                    
                    // Pull out the setting name.
                    string sSetting = llToLower( llStringTrim( llList2String( sLineData, 0 ), STRING_TRIM ) );
                    
                    // Pull out the setting value.
                    string sValue = llStringTrim( llList2String( sLineData, 1 ), STRING_TRIM );
       
                    // Owner?
                    if ( sSetting == "owner" )
                    {
                        g_lOwners += [ sValue ];
                        g_lAccess += [ sValue ];
                        llWhisper( 0, "Owner: " + sValue );
                    }
                    // Access list?
                    else if ( sSetting == "access" )
                    {
                        g_lAccess += [ sValue ];
                        llWhisper( 0, "Access: " + sValue );
                    }
                    // Range?
                    else if ( sSetting == "range" )
                    {
                        g_nRange = (float) sValue;
                        llWhisper( 0, "Range: " + sValue );
                    }
                    // Frequency?
                    else if ( sSetting == "frequency" )
                    {
                        g_nFrequency = (float) sValue;
                        llWhisper( 0, "Frequency: " + sValue );
                    }
                }
                
                // Read the next line.
                g_keyConfig = llGetNotecardLine( CONFIG_FILE, ++g_iConfig );
            }
            else
            {
                // Config read. Get protecting.
                state Protect;
            }
        }
    }
}

//////////////////////////////////////////////////////////////////////
// Paused state -- does nother but waits to be activated again.
state Paused
{
    //////////////////////////////////////////////////////////////////
    // State entry.
    state_entry()
    {
        // Tell the user we're paused.
        llWhisper( 0, "Security protection is paused." );
        
        // And add some hover text too.
        llSetText( "Security system paused.", < 1.0, 0.0, 0.0 >, 1.0 );
    }
    
    //////////////////////////////////////////////////////////////////
    // Handle a touch.
    touch_end( integer num_detected )
    {
        // If it's an owner who touched us...
        if ( IsOwner( llDetectedName( 0 ) ) )
        {
            // Jump back to the protect state.
            state Protect;
        }
    }
    
    //////////////////////////////////////////////////////////////////
    // Detect change.
    changed( integer change )
    {
        // If the inventory has changed...
        if ( change & CHANGED_INVENTORY )
        {
            // ...start over.
            state Configure;
        }
    }
}

//////////////////////////////////////////////////////////////////////
// Protect state -- does the scanning and ejecting.
state Protect
{
    //////////////////////////////////////////////////////////////////
    // State entry.
    state_entry()
    {
        // Let the user know what's happening.
        llWhisper( 0, "Security protection is enabled." );
        
        // Remove any hover text.
        llSetText( "", < 1.0, 1.0, 1.0 >, 0.0 );
        
        // Start the sensor.
        llSensorRepeat( "", "", AGENT_BY_LEGACY_NAME, g_nRange, PI, g_nFrequency );
    }
    
    //////////////////////////////////////////////////////////////////
    // State exit.
    state_exit()
    {
        // Make sure the sensor is removed.
        llSensorRemove();
    }

    //////////////////////////////////////////////////////////////////
    // Handle the sensor result.
    sensor( integer num_detected )
    {
        integer i;
        
        // For each detected avatar...
        for ( i = 0; i < num_detected; i++ )
        {
            // If they are over our land...
            if ( llOverMyLand( llDetectedKey( i ) ) )
            {
                // If they're not on the access list...
                if ( !HasAccess( llDetectedName( i ) ) )
                {
                    // Eject them.
                    Eject( llDetectedKey( i ) );
                }
            }
        }
    }
    
    //////////////////////////////////////////////////////////////////
    // Handle a touch.
    touch_end( integer num_detected )
    {
        // If it's an owner who touched us...
        if ( IsOwner( llDetectedName( 0 ) ) )
        {
            // Jump to the paused state.
            state Paused;
        }
    }
    
    //////////////////////////////////////////////////////////////////
    // Detect change.
    changed( integer change )
    {
        // If the inventory has changed...
        if ( change & CHANGED_INVENTORY )
        {
            // ...start over.
            state Configure;
        }
    }
}