#!/usr/bin/perl
######################################################################
#
#   Copyright 1999-2000 Phone.com, Inc.  All rights reserved.
#
#   Subject to the terms and conditions of the SDK License Agreement, 
#   Phone.com, Inc. hereby grants you the right to use the UP.SDK 
#   software and its related documentation.
#
#   The Phone.com name and logo and the family of terms carrying the "UP."
#   prefix are trademarks of Phone.com, Inc. All other brands and product 
#   names may be trademarks of the respective companies with which they 
#   are associated.
#
######################################################################

######################################################################
#
# List manager
#
# This sample application demonstrates list management in WML apps
#
# Key concepts illustrated include:
#       - Implement list 'paging' to limit output to 9 entries in a card
#       - Cache management
#       - Deck navigation and deck history
#       - Calling contexts (spawn, send, receive, throw, catch, exit)
#       - Handling international character set data:
#          - Posting arguments using the 'postfield' element
#          - Setting the Content-type charset in the HTTP response
#
# Application Flow:
#
# A user's navigation through the application is represented as state
# machine transitions.  Each URL encodes the state to transition to in
# the "NEXT" (Next State) argument.  The main routine of the application
# examines the NEXT argument and either returns the appropriate static
# WML deck, or invokes the appropriate routine to compute the dynamic
# WML deck.
#
# The NEXT state could be one of the following:
# LIST      - To display a particular page of the list
# ADD       - To add a new item to the end of the list
# DEL       - To delete the selected item from the list
# EDIT      - To edit the selected item in the list
# MENU      - To diplay the list of actions that could be performed on the list
# EDITENTRY - To display an entry card to edit an entry
#
# Note that each of the above NEXT states could have other 
# state-specific CGI variables. For example, NEXT=ADD will include
# a CGI variable ITEM to indicate the new value to be added
#
######################################################################

######################################################################
#
# SDK SAMPLE APPLICATION CONFIGURATION
#
# To configure this application to run on your system:
#
# 1. On Unix, change the first line of the file to use the 
#    path for the Perl interpreter on your system
#
# 2. On NT, verify that the .cgi file extension is mapped to
#    the Perl interpreter on your system or rename the file
#    extension to the default extension that is mapped to the
#    Perl interpreter on your system (e.g. .pl)
#
# 3. Make sure that the list.data file is writable by the process
#    running the HTTP request (usually user 'nobody' on Unix).
#
######################################################################

######################################################################
# AppUtils Loading and Configuration
######################################################################
# Include the apputils directory which has all the SDK utilities
BEGIN { push (@INC, "../apputils"); } 	# Path to SDK apputils

# Load required application utilities
require 'FileUtils.pl';
require Digest;

######################################################################
# Constants
######################################################################
# Employees records file
$DATA_FILE = './list.data';
$LOCK_FILE = './lock.data';

# Character set of application content
$CONTENT_CHARSET = "charset=ISO-8859-1";

#
# The WML Decks
#

# This $LIST deck is returned when the user requests
# to display the list of items - (NEXT=LIST)
$LIST ='
<wml>
   <card id="List">
      <do type="accept">
         <go href="?NEXT=MENU">
            <setvar name="pos" value="%s"/>	
         </go>
      </do>
      <do type="prev">
         %s
      </do>
      <p>
         Items [%s..%s]
         <select name="pick">%s
         </select>
      </p>
   </card>
</wml>';

$LIST_ENTRY = "\n".
'          <option value="%s">%s</option>';
$MORE_ENTRY = "\n".
'          <option onpick="?POS=%s">More...</option>';

# The $MENU deck is returned when the user selects the 'Menu' soft key
# from the list deck - (NEXT=MENU)
#
# The select list performs each of the actions.  Depending on the
# action, one or more of the following arguments are sent to the
# server:
#
#    POS  - Current deck position in the list
#    NAME - Index of item within the current deck
#    ITEM - Text for new or edited item
#
# The text entry and confirmation cards are called as sub-contexts
# using the 'spawn' task.  This allows the cards to be removed from
# the history stack automatically before the new list deck is returned.
#
$MENU_DECK ='
<wml>
   <card>
      <p>
         Actions
         <select name="menu">
            <option>
               <onevent type="onpick">
                  <spawn href="#additem">
                     <onevent type="onexit"> 
                        <go href="?NEXT=ADD&amp;POS=$(pos)">
                           <postfield name="ITEM" value="$(item)"/>
                        </go>
                     </onevent>
                     <receive name="item"/>
                     <catch/>
                  </spawn>
                  </onevent>
               Add (to end)
            </option>

          <option>
             <onevent type="onpick">
                <spawn href="?NEXT=EDITENTRY&amp;NAME=$(pick)">
                   <onevent type="onexit"> 
                      <go href="?NEXT=EDIT&amp;NAME=$(pick)&amp;POS=$(pos)">
                         <postfield name="ITEM" value="$(item)"/>
                      </go>
                   </onevent>
                   <receive name="item"/>
                   <catch/>
                </spawn>
             </onevent>
             Edit
          </option>

          <option>
             <onevent type="onpick">
                <spawn href="#confirm" 
                   onexit="?NEXT=DEL&amp;NAME=$(pick)&amp;POS=$(pos)">
                   <catch/>
                   <catch name="abort action">
                   <onevent type="onthrow">
                      <prev/>
                   </onevent>
                   </catch>
                </spawn>
             </onevent>
             Delete
	  </option>
       </select>
    </p>
  </card>

  <card id="additem">
     <do type="accept">
        <exit>
           <send value="$(item)"/>
        </exit>
     </do>
     <p>
        Enter item:
        <input name="item"/>
     </p>
  </card>

  <card id="confirm">
     <do type="accept" label="Yes">
        <exit/>
     </do>
     <do type="options" label="No">
        <throw name="abort action"/> 
     </do>
     <p>
        Are you sure you want to delete this item?
     </p>
  </card>
</wml>';

# This deck provides an entry card with the current value of the item
# as default to allow editing of the item.
# The reason that this is implemented as a separate deck is so
# we can read the text value for the selected item from the list file 
# and then set it as the default input value in the card.
$EDIT_ENTRY_DECK = '
<wml>
   <head>
      <meta forua="true" http-equiv="cache-control" content="max-age=0"/>
   </head>
   <card id="edititem">
      <do type="accept">
         <exit>
            <send value="$item"/>
         </exit>
      </do>			
      <p>
         Edit item:
         <input name="item" value="%s"/>
      </p>
   </card>
</wml>';


#Generic display card to indicate errors
$INFOCARD = '
<wml>
   <head>
      <meta forua="true" http-equiv="cache-control" content="max-age=0"/>
   </head>
   <card>
      <p>
         %s
      </p>
   </card>
</wml>';

######################################################################
# State definitions
#
# main uses these definitions to determine next state
######################################################################
# Static states {state, deck}
%StaticStateArray = (
		    "MENU", $MENU_DECK
			 );

# Dynamic states {state, routine}
%DynamicStateArray = (
			  "LIST", "listItems",
			  "ADD", "addItem",
			  "EDIT", "editItem",
			  "DEL", "deleteItem",
			  "EDITENTRY", "displayEditCard",
			  "EDITRETURN", "editReturn",
			  );

#Call the main function to begin execution
&main;

######################################################################
#
# main Function
#
# When a URL request is received, the application checks the value of 
# NEXT to determine the next action. The next action may be to display 
# a static WML deck or to execute a function to generate dynamic WML.
# This function implements this logic
#
# This function also reads the CGI variables into %cgiVars for the
# rest of the application to use
#
###################################################################### 
sub main
{

	# Parse the HTTP CGI variables into the associative array %cgiVars
	%cgiVars = &AppUtils::ParseCGIVars();

	# Get the next state and look it up
	$nextState = $cgiVars{"NEXT"};
	if( !defined($nextState) )	{
		$nextState = "LIST";
	}
 
	# Transition to the next state
	$deck = $StaticStateArray{$nextState};
	if( $deck ne "" ) {
		#Found a static deck to return for the NEXT state, just return it
		&AppUtils::OutputDeck($deck,$CONTENT_CHARSET);
	}
	else {
		$subRoutine = $DynamicStateArray{$nextState};
		if ($subRoutine ne undef) {
			# Found a function to execute for the NEXT state, execute it
			# to generate dynamic WML
			&$subRoutine();
		}
		else {
			#Invalid NEXT state, return Error deck
			&AppUtils::ErrorExit("Invalid URL", "");
		}
	}
}

######################################################################
#
# listItems
#
# Called if NEXT=LIST
# Uses the createListDeck function to create the list
#
######################################################################
sub listItems
{
	my ($nextDeck,$pos) = &createListDeck;
	&AppUtils::OutputDeck($nextDeck,$CONTENT_CHARSET);
}

######################################################################
#
# createListDeck
#
# Common function used to create the list either when NEXT=LIST
# or to display the updated list after performing an context
#
# Creates a list of upto 9 items starting from
# the current position indicated in the CGI variable POS
#
# POS indicates the position of the item in the list.
# Since each page displays 9 entries, the first page is accessed 
# with POS=0 (or no POS variable), the second with POS=9, the third 
# with POS=18 and so on. When this function determines that the list
# has more than POS+9 entries, it creates a "More" choice entry at the
# end of the page with a new POS value of POS+9. So when the user clicks
# "More", a request is made from the browser to this function with the
# new POS value to retrieve the next page.
#
# Since the application redisplays the list after performing an
# context (ADD, DEL, EDIT), we also explicitly provide an PREV action.
# The PREV action calls this function with a position value of POS-9
# This will cause the previous 9 entries to be displayed.
#
######################################################################
sub createListDeck
{
    	my ($item, $choices);
	my @list;
	my $PAGE_SIZE = 9;

	my $start, $end, $loopCount, $totalItemCount;
	my $moreString, $prevString;

	#Read the list from the data file
	&readList(\@list);
	$totalItemCount = $#list+1;

	#Establish $start and $end to indicate the first and last items
	#to be shown on this page based on the current POS
	$start = $cgiVars{'POS'};

	if (! defined($start)) {
		$start = 0;
	}
	
	#If the page being requested is beyond the total number
	#of pages, move $start to point to the last page. This will take
	#care of requests to re-display the last page when the
	#last item in the page has just been deleted
	while ($start >= $totalItemCount && $start >= $PAGE_SIZE) {
		$start -= $PAGE_SIZE; 
	}
	
	#Calculate the last item to be displayed in the page
	#and whether or not a "More" entry must be added for the next page
	if ($start+$PAGE_SIZE < $totalItemCount) {
		$end = $start+$PAGE_SIZE;
		$moreString = sprintf($MORE_ENTRY, $end);
	}
	else {
		$end = $totalItemCount;
		$moreString = "";
	}

	#Define explicit "Prev" navigation in the list cards
	#since we are jumping back to the list cards after an
	#action (e.g. Delete) has just been performed
	if ($start == 0) {
		$prevString = "<exit/>";
	}
	else {
		if ($start == $PAGE_SIZE) {
			$prevString = '<go href="?"/>';
		}
		else {
			$prevString = '<go href="?POS=' . ($start-$PAGE_SIZE) . '"/>';
		}
	}

	# Build the list as a series of WML select options 
	# for the range of items identified by $start and $end
	# As you add the items encode the item value in URL encoded
	# form using HTTPEscapeString (i.e. convert an & to %26 etc.)
	# Also encode the text being displayed in the WML card
	# using DeckEscapeString (i.e convert & to &amp; etc.)
	my ($key, $text);
	for ($loopCount = $start;$loopCount < $end;$loopCount++)
	{
		$item = $list[$loopCount];
		chomp $item;
		#The data source stores the data as a key and value
		#separated by the | symbol.
		($key, $text) = split(/\|/,$item);
		$choices .= sprintf($LIST_ENTRY, 
						&AppUtils::HTTPEscapeString($key), 
						&AppUtils::DeckEscapeString($text));
	}
	$choices .= $moreString;

	if ($choices ne '') {
	    return (sprintf($LIST, $start, $prevString, $start+1, 
						$end, $choices),$start);
	}
	else {
		return (sprintf($INFOCARD, "No match found"),$start);
    	}
}

######################################################################
# addItem
# NEXT=ADD
#
# Adds a new item to the list
#
######################################################################
sub addItem
{
   my $item = $cgiVars{'ITEM'};
   my @list;
   my $key;
	
   # Use a lock file to avoid simultaneous access by others
   &AppUtils::OpenWithLock(\*LOCK_FH, ">$LOCK_FILE", 'WRITE');

   # Then read the current list, generate a unique key for the new item,
   # Add the new item to the list and write the updated list back to the file
   &readList(\@list);
   $key = &findNextKey(\@list);
   push (@list, "$key\|$item\n");
   &writeList(\@list);

   # Unlock the data file for others to use
   &AppUtils::CloseWithLock(\*LOCK_FH);

   # Use actyReturn to create the output digest
   &actyReturn;
}

######################################################################
# deleteItem
# NEXT=DEL
#
# Deletes the item identified by the CGI var NAME
#
######################################################################
sub deleteItem
{
	my $itemKey = $cgiVars{'NAME'};
	my @list;
	my $position = 0;

	# Use a lock file to avoid simultaneous access by others
	&AppUtils::OpenWithLock(\*LOCK_FH, ">$LOCK_FILE", 'WRITE');

	# Read the list, find the item to be deleted, remove it from
	# the list and then write out the updated list
	# Call actyReturn to create the output digest
	&readList(\@list);
	foreach $item (@list) {
		if ($item =~ m/^$itemKey\|/) {
			splice (@list, $position, 1); 	
			&writeList(\@list);
			&AppUtils::CloseWithLock(\*LOCK_FH);
			return &actyReturn;
		}
		$position++;
   	}

	#Unlock the data file for others to use
	&AppUtils::CloseWithLock(\*LOCK_FH);
	&AppUtils::OutputDeck(sprintf($INFOCARD,"$itemKey not found"),$CONTENT_CHARSET);
}

######################################################################
# editItem
# NEXT=EDIT
#
# Renames the item identified by NAME to the new value in ITEM
#
######################################################################
sub editItem
{
	my $key = $cgiVars{'NAME'};
	my $newValue = $cgiVars{'ITEM'};
	my @list;
	my $position = 0;
	print ($key);

	# Use a lock file to avoid simultaneous access by others
	&AppUtils::OpenWithLock(\*LOCK_FH, ">$LOCK_FILE", 'WRITE');

	# Read the list, find the item to be updated, change it
	# and then write out the updated list
	# Call actyReturn to create the output digest
	&readList(\@list);
	foreach $item (@list) {
		if ($item =~ m/^$key\|/) {
			$item = "$key\|$newValue\n";
			&writeList(\@list);
			&AppUtils::CloseWithLock(\*LOCK_FH);
			return &actyReturn(1);
      		}
		$position++;
   	}
	# Unlock the data file for others to use
	&AppUtils::CloseWithLock(\*LOCK_FH);
	&AppUtils::OutputDeck(sprintf($INFOCARD,"KEY=$key,ITEM=$newValue. Not found"),$CONTENT_CHARSET);	
}

######################################################################
# editReturn
# NEXT=EDITRETURN
#
# Returns from the sub-context with the updated current page
#
######################################################################
sub editReturn
{
	return &actyReturn(1);
}

######################################################################
# displayEditCard
# NEXT=EDITENTRY
#
# Displays an entry card with the current value as the default
#
######################################################################
sub displayEditCard
{
	my $lookupKey = $cgiVars{'NAME'};

	# Use a lock file to avoid simultaneous access by others
	&AppUtils::OpenWithLock(\*LOCK_FH, ">$LOCK_FILE", 'WRITE');

	# Read the list, find the item to be updated, and create the entry
	# card with the items current text
	&readList(\@list);
	foreach $item (@list) {
		chomp $item; #remove trailing \n
		($key, $text) = split(/\|/, $item);
		if ($lookupKey == $key) {
			&AppUtils::CloseWithLock(\*LOCK_FH);
			return &AppUtils::OutputDeck(
				sprintf($EDIT_ENTRY_DECK,&AppUtils::DeckEscapeString($text)),$CONTENT_CHARSET);
		}
	}
	&AppUtils::OutputDeck(sprintf($INFOCARD,
			"No item found with key $lookupKey"),$CONTENT_CHARSET);
}

######################################################################
# actyReturn
#
# Generic function used to return from each of the three
# sub contexts (ADD, DEL, EDIT) and to return to the 
# appropriate list page deck
# 
# In addition to returning from the context, we need
# to display an updated list. So we will use digests to
# invalidate appropriate list decks in the cache and to
# replace them with updated decks.
#
######################################################################
sub actyReturn
{
	# Variable to indicate whether or not to invalidate the list decks
	my $noinvalidate = shift;

	my $digest;
	$digest = new Digest;

	# Create the list deck that should be displayed next
	my ($nextDeck,$pos) = &createListDeck;

	# Create the URL of the list being replaced based on current POS
	my $nextURL;
	if ($pos != 0) {
		$nextURL = "?POS=$pos";
	}
	else {
		$nextURL = "?";	
	}

	# For ADD and DEL, invalidate all the list decks
	# For EDIT just invalidate current page - there is no need to 
	# invalidate previous or next pages since there has been no change to them
	if (! $noinvalidate) { 
		# Invalidate all the decks in the cache that contain
		# POS in the URL - i.e. all the list decks except the first page
		# URL of "?" indicates current application and "?POS" indicates
		# all URLs in the app that contain the POS variable no matter what
		# its value
		$digest->addCacheOp($Digest::InvalSvc, "?POS");
		if ($pos != 0) {
			# Invalidate the only other list deck that does not have
			# POS in the URL - i.e. the first page
			$digest->addCacheOp($Digest::InvalURL, "?");
		}
	}
	
	# Add the updated deck to be displayed next
	$digest->addDeck($nextURL, $nextDeck, $CONTENT_CHARSET);

	&AppUtils::OutputDigest($digest->asString());
}

######################################################################
# readList
#
# Read the list from the data source into the list reference passed in
#
######################################################################
sub readList
{
	my $listref = shift;

	open (LIST_DATA, "<$DATA_FILE");
	@$listref = <LIST_DATA>;
	close (LIST_DATA);
}

######################################################################
# writeList
#
# Write the list out to the data source from the list reference passed in
#
######################################################################
sub writeList
{
	my $listref = shift;

	open (LIST_DATA, ">$DATA_FILE");
	print LIST_DATA @$listref;
	close (LIST_DATA);
}

######################################################################
# findNextKey
# 
# The purpose of this function is to generate the a unique key
# to use to insert the new item to the list
#
# This implementation generates a unique id using the key for the last 
# item in the current list. Since our list key is just a series of numbers
# and since our add routine allows appends items to the end of the list
# we can find the key for the last item in the list and add 1 to it
# to get the next unique key
#
######################################################################
sub findNextKey
{
	my $listref = shift;

	my $itemCount = $#$listref;
	my $lastItem = $$listref[$itemCount];
	my ($key, $text) = split(/\|/, $lastItem);

	return ($key+1);

}

###########################################
#Test functions
###########################################
sub testFindNextKey
{
	my @list = ();
	push (@list, "1|test1\n");
	push (@list, "2|test1\n");
	push (@list, "3|test1\n");

	print "Next key is:", &findNextKey(\@list), "\n";;
}

