#!/usr/bin/perl  --
# $Id: vicq,v 1.103 2002/02/03 17:38:15 gonzo Exp $

##########################################################
# 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.
# See LICENSE for details
##########################################################


# use strict;
use Term::ReadLine;
use Getopt::Std;
use Socket;
use POSIX qw(mktime getcwd);
package main;
use locale;
#use Data::Dumper;

my %config;
my %opt_info;
my %file_info;
my %contacts_info;
my %aliases_info;
my %deleted_aliases;
my %wp;
my %requests;
my %files;
my %_command_handlers=();
my %_message_handlers=();
my $altargs;
my $config_loaded = 0;
my $section = '';
my $Id = '$Id: vicq,v 1.103 2002/02/03 17:38:15 gonzo Exp $';
my $nomodule = 0;
my %connection_types = (
							1 => 'firewall/https proxy',
							2 => 'socks4/socks5 proxy',
							4 => 'normal connection'
				);
			
$| =1;


=head1 NAME

vicq - console ICQ2000 client

=head1 SYNOPSIS

  vicq [-u UIN] [-rbo] [-c config] [-t delay]

=head1 DESCRIPTION

B<vicq> is console icq2000 client, simmilar to micq

=head1 OPTIONS

=over 4

=item B<-u> I<UIN>

When invoked with B<-u> option, vicq expects to read single message
from B<stdin> and sends it to the specified I<UIN>

=item B<-c> I<config>

Use alternative config instead of ~/.vicq/config

=item B<-b>

Do not send contact list and visible/invisible list (use in a non-interactive mode)


=item B<-o>

Do not request offline messages (use in a non-interactive mode)


=item B<-t> I<delay>

Delay between commands in non-interactive mode

=item B<-r>

Register new UIN on startup

=back

=head2 Non-interactive mode

You can use vicq in non-interactive mode by writing script and  sending 
it on stdin of programm, for example:

 echo "msg gonzo/hi" | vicq -b -o

 or 
 
 $cat > xx
 msg gonzo/hi
 msg gonzo 
 multi
 line
 message
 .
 ^D
 $cat xx | vicq -b -o -t 5

On slow connections you must set delay between commands using B<-t> option
because some message disappears ( i dont sure that source of this problem 
slow connections, may be ICQ server drops flood messages)


=head1 CONFIGURATION

Config file (~/.vicq/config) consists of several sections described below. 
Each section starts with header line like this: '[section_name]'. Lines 
starting with '#' are comments.  You can split entire config in several 
files using 'include' directive. BUT! You should place whole section in 
one file, the section header should be placed in the same file.  Example 
of using include you can find  at http://www.gonzo.kiev.ua/projects/vicq/eg/

=head2 [options]

The B<[options]> section contains configuration variables. Each variable is
in "name=value" format (case-sensitive).

Configuration variables:

=over 4

=item B<uin> (decimal)

User's UIN

=item B<password> (string)

User's password

=item B<keep_config> (decimal)

If you dont want to save config on exit set this variable to 1: 
B<keep_config=1>

=item B<encoding> (string)

Valid values 'koi','win' and 'translit', depending on your terminal

=item B<auto_info> (integer)

If not zero, vICQ will request info on every UIN which is not in ContactList automaticaly

=item B<hide_ip> (decimal)

if not zero ICQ will not show your IP

=item B<prompt> (string)

Main vICQ prompt template. The following variables will be parsed and may 
be used in prompt string:
 %S - by short description of your status
 %U - by your uin
 %h - current hour
 %m - current minute
 %s - current second
 %% - '%'

Example:

 prompt=vICQ(%S)/%U[%h:%m:%s]E<gt>

=item B<sms_phonebook> (string)

Path to external file with sms phones in B<[phones]> section format.
Section tag ('[phones]') is must for this file.


=item B<separator_length> (integer)

Length of separator line

=item B<disable_empty_separators> (integer)

if not equal to B<0> disables separator without text


=item B<history_entries> (integer)

Number of messages which displays in  user history, if its negative - displays all history


=item B<mode> (string)

Valid values B<normal> or B<silent>. In B<silent> mode status_change events does not appear on screen.

=item B<status> (string)

Initial status. Valid values are:

 Online
 Free_For_Chat
 Away
 Not_Avalible
 Occupied
 Do_Not_Disturb
 Invisible

=item B<colors> (integer)

If this is not B<0>, vicq will use colors.

=item B<colored_history> (integer)

If this is not B<0>, vICQ use colors for history output(You can set 
you PAGER environment variable to B<less -R> for using color)

=item B<separator_color> (string)

Color of the separator line

=item B<separator_title_color> (string)

Color for the title separator

=item B<nick_color> (string)

Color for highlighting nicknames

=item B<uin_color> (string)

Color for highlighting uins

=item B<status_color> (string)

Color for highlighting status

=item B<time_color> (string)

Color for highlighting time

=item B<message_color> (string)

Message color

=item B<their_history_color> (string)

Color for incoming events in history

=item B<my_history_color> (string)

Color for outgoing events in history

Valid colors are:

 NORMAL
 YELLOW
 MAGENTA
 WHITE
 BLACK
 RED
 GREEN
 BROWN
 BLUE
 CYAN
 LIGHT_CYAN
 LIGHT_RED
 LIGHT_GREEN
 LIGHT_BLUE

And three text attributes for monochrome terminals:

 BOLD
 UNDERSCORE
 REVERSE

=item B<player> (string)

Command for playing sounds (see B<[sounds]> section)
'%f' in player path will be replaced with filename 
chosen according to the B<[sounds]> section

Example: 

 player=/path/to/player -f %f -o /dev/dsp 

=item B<browser> (string)

Command for browsing received URLs:
'%u' in browser command will be replaced with last recieved url

Example:

 browser=/path/to/browser -navigator %u 

=item B<https_proxy> (integer)

If not B<0>, vICQ with work through HTTPS-capable proxy

=item B<socks_proxy> (integer)

If not B<0>, vICQ with work through SOCKS-capable proxy(v5 only available for
this moment)

=item B<proxy_host> (string)

HTTPS/SOCKS proxy host

=item B<proxy_port> (decimal)

HTTPS/SOCKS proxy port

=item B<proxy_login> (string)

Login for HTTPS/SOCKS proxy authorization (if ommited - don't use aythorization for SOCKS proxy)


=item B<proxy_password> (string)

Password for HTTPS/SOCKS proxy authorization

=item B<proxy_force_https_port> (decimal)

if B<1>, always connect to login.icq.com on port B<443>, regardless 
of what specified in the reconnection messages

Example of HTTPS proxy configuration:

 https_proxy=1
 proxy_port=3128
 proxy_host=10.25.0.99
 proxy_force_https_port=1

Example of SOCKS proxy configuration(without authorization):

 socks_proxy=1
 proxy_port=1080
 proxy_host=10.25.0.99


=item B<log_path> (string)

Directory, which contains history files. 

Example:

 log_path=~/.vicq.log

=item B<log_type> (string)

Specifies method which logs saves. Its a string if its empty - 
don't log, else calls methods correspondingly chars which appears 
in strings. Valid chars:

 a - log to <log_path>/vicq.log
 u - log to <log_path>/<uin>.log
 s - log status changes
 n - make newlines between log records
 l - make symlinks <nick>.log to <uin>.log

Default: log_type=u.

Example:

 log_type=usln

=item B<micq_like_completion> (decimal)

If not B<0> mICQ style completion is using: cicles through UINs used
in previous B<msg> commands


=item B<autosplit> (decimal)

If not B<0> messages longer than 450 chars will be splitted  into several 
parts, otherwise - message will be truncated to 450 chars

=back

=head2 [aliases]

The B<[aliases]> section contains run-time options. Its format is 'alias=command'.

Example:

 m=msg

=head2 [phones]

The B<[phones]> section contains phonebook for SMS messages in 'nick phone'
format.  You can use these aliases in 'sms' command

Example:

 squid +380xxxxx
 ReY +380xxxxxx


=head2 [events]

The B<[events]> section contains events hooks. Its format is
 B<event[/nick][|my_status] command> or
 B<[!][|]event[/nick][|my_status] "command1","command2"> 

where B<event> is the type of event, B<nick_or_uin> sender's nick or uin, B<my_status> your status (all parameters could be glob-style wildcard), if optional parameters  B<nick_or_uin> or  B<my_status> is ommited then they acts as B<*>(any). B<command> is an command name to be executed when event occurs and event type, sender's info and your status  matches specified parameters.B<!> in the beggining means that hook is inactive. B<|> in the beginning means that event does not break checking after match. The following variables
will be parsed and may be used in 'command' string:
 B<%e> - by type of event
 B<%u> - by sender's uin
 B<%n> - by sender's nick
 B<%t> - by message text (or status value if type of event is 'status_change') Y
ou can use uppercase modifiers to make them colored e.g.
 B<%E> - colored type of event
 If you want run external command - use B<!> in command definition.

Examples:

 * ! echo 'Event %e from %n(%u) : %t' | mail mymail@myhost.com

(send email to mymail@myhost.com with the full description of the recieved event)

 text_message msg %u/i will read this message later

(simple autoresponder)


 text_message|online msg %u/hi\nI am  online
 text_message/GonZo|away msg %u/hi GonZo\nI am away
 
(extended autoresponder)

List of events types:
 sms_delivery_receipt
 status_change
 URL
 sms_message
 contacts_request
 email_message
 text_message
 offline_text_message
 contacts
 add_message
 auth_request
 sms_response
 sms_message
 user_short_info
 user_info_not_found
 ack_offline
 set_permissions_ack
 user_info_unknown
 user_info_about
 user_info_main
 user_info_extra_emails
 set_main_info_ack
 wp_final_result_info
 wp_empty
 user_info_homepage
 wp_result_info
 user_info_past_background
 user_info_personal_interests


=head2 [sounds]

Section B<[sounds]> is similar to B<[events]> section, allowing you
to specify audio files played upon various incoming events. Playback
will be done through the "player" application defined in the B<[option]>
section, with B<%f> token substituted with the proper filename.

=head2 [contacts]


B<[contacts]> section lists UINs, format is the same as in .micqrc:

 UIN Nickname

To add user to visible list add asterisk (B<*>) before UIN (see vicqrc.example).
If you want be invisible to user add tilde (B<~>) before UIN.
If you want create alias for UIN/Nick just add aliases in the line below UIN (see vicqrc.example)


=cut

=head1 INTERACTIVE OPERATIONS

=head2 Completion & nick handling

You can use TAB to complete command or nick, if your nicks contains spaces:
use double quotes to handle this nicks 
eg: 
 vICQ>msg "anri <TAB>

=head2 Messages handling

=over 4

=item B<msg> I<nick|UIN [/msg]>

send an instant ICQ message

=item B<sms> I<nick|phone [/msg]>

send SMS to specified person

=item B<r>

replie to last received message

=item B<a>

send a message to the last person you sent a message

=item B<auth> I<nick | UIN>

send an authorization to specified UIN

=item B<url> I<nick | UIN> [URL]

send an URL to specified person

=back

=head2 Status managment

=over 4

=item B<online>

change status to 'Online'

=item B<inv>

change status to 'Invisible'

=item B<away>

change status to 'Away'

=item B<na>

change status to 'Not availabe'

=item B<occ>

change status to 'Occupied'

=item B<dnd>

change status to 'Do Not Disturb'

=item B<ffc>

change status to 'Free for chat'

=back

=item B<offline>

change status to 'Offline/Disconnect'

=head2 Contact list managment

=over 4

=item B<add> I<UIN nick>

add I<UIN> as I<nick> to contact list

=item B<w>

display the current status of every person in your contact list

=item B<e>

display the current status of every online person in your contact list

=item B<togvis> I<UIN | nick>

add/remove user to/from visible list

=item B<toginvis> I<UIN | nick>

add/remove user to/from invisible list

=item B<history> I<UIN | nick [count]> 

display last I<count> entries of the user's history. If I<count> is
negative shoes all entries

=item B<finger> I<UIN | nick>

display user's UIN, nick, current status, IP(and resolved host if 
available), dirrect connection info, and client version

=back

=head2 Miscellaneous commands

=over 4

=item B<help> I<command>

display help on command

=item B<!> I<OS command>

execute external command

=item B<save>

save config

=item B<info> I<UIN | nick>

send user info request

=item B<search> I<email@host.domain>

searche for ICQ user

=item B<silent>

toggle to silent mode - change status events does not displays

=item B<normal>

toggle to normal mode - change status events displays

=item B<view>

open last recieved URL with I<browser> command

=item B<set> I<[key[=value]]>

shows/sets config variables values

=item B<wpset> 

set basic White Pages info 

=item B<clear> 

clear screen

=item B<alias> I<[alias = ] [command]>

 show/change command aliases, ex:
 alias q=quit

=item B<last> I<UIN | nickname>

 Show last message from UIN or nickname

=item B<echo> I<string>

 Sends to the output I<sting>. Use mainly in hooks

=item B<permissions> I<[+|-][web | auth]>

 Set 'web aware/need authorization' on/off, ex:
 permissions +auth -web

=item B<version>

 Display current version of vICQ

=item B<event> I<[ on | off event_id]>  I<add event_spec>

 activate/deactivate event  or add new event

=item B<reg> I<password>

 register new UIN with specified password 

=back

=head2 Message editing

Input B<.> in the empty line to end message or B<#> to cancel message

=head1 AUTHOR

Alexander Timoshenko E<lt>gonzo@ukrweb.netE<gt>

=cut


##############################################################################
##
## Configuration
##
##############################################################################

$ENV{"PERL_RL"} = " o=0";
my %contacts;
my %macroses;
my %smsphones;
my $done = 0;
my $redisplay = 0;
my $lastactivity;

my %opts;
my %prefs;
my %wpinfo;
my %colors = (
	LIGHT_RED  => "\033[1;31m",
	LIGHT_GREEN => "\033[1;32m",
	YELLOW => "\033[1;33m",
	LIGHT_BLUE => "\033[1;34m",
	MAGENTA => "\033[1;35m",
	LIGHT_CYAN => "\033[1;36m",
	WHITE => "\033[1;37m",
	NORMAL => "\033[0m",
	BLACK => "\033[0;30m",
	RED => "\033[0;31m",
	GREEN => "\033[0;32m",
	BROWN => "\033[0;33m",
	BLUE => "\033[0;34m",
	CYAN => "\033[0;36m",
	BOLD => "\033[1m",
	UNDERSCORE => "\033[4m",
	REVERSE => "\033[7m",
	
);

my $last_message_from = 0; 
my $last_message_to = 0;
my $last_message_url = '';
my $requested_uin = 0;
my $gnu_readline = 0; # Are we using Term::ReadLine::Gnu
my $term;
my $save_password=0;
my $search_result = '';

# ESC sequences to delimit non-printable chars
my $ignore_start=''; 
my $ignore_stop='';

my $keepalive=1;
my $search_info;
my %last;
# Message appears when trying to work offline
my $offline_error = "You are offline. Connect first, please";

# Array of external programs which should be called upon event.
# Contain references to two-element lists where first part is regular
# expression which match event type and second one is command.
# Command could contain specifiers %e, %u %n and %t which are replaced
# with event type, uin, nick and text respectively. For status change
# event %t is new status of remote user.
my @ExternalHooks=();
my @DeletedHooks=();
my @SoundHooks=();
# Command aliases to be preserved on write_config
my %aliases;

# Structure of help susbsystem
my %subtopics = (
	'status' => ['online','away','na','dnd','occ','ffc','inv','offline'],
	'messages' => ['msg','r','a','sms','auth','url','last'],
	'contacts' => ['add','togvis','history','finger','w','e'],
	'misc' => ['!','help','save','info','silent','normal',
		'view','set','search','wpset','alias' , 'clear','echo',
		'permissions', 'version','event']
);
# Root subtopics description
my %descriptions = 
(
	'status' => 'Status change commands',
	'messages' => 'commands for message handling',
	'contacts' => 'contact list managing',
	'misc' => 'miscellaneous commands'
);

#Commands descriptions
my %helps = (
	'msg' =>  "[UIN | nickname][/message]",
	'sms' =>  "phonenumber[/message]",
	'add' =>  "UIN nickname",
	'url' => "[UIN | nickname]",
	'view' => " \nview last received URL",
	'silent' => 'Changes mode to silent(no status messages',
	'normal' => 'Changes mode to normal(status messages enabled)',
	'submit' => "\n(debug command)",
	'info' => "[UIN | nickname]",
	'togvis' => "[UIN | nickname]\nadds/removes user to/from visible list",
	'toginvis' => "[UIN | nickname]\nadds/removes user to/from invisible list",
	'finger'=>  "[UIN | nickname]\n gives information about person on your contact list\n",
	'inv' => "\nChange status to invisible",
	'na' =>  "\nChange status to Not Available",
	'dnd' =>  "\nChange status to Do Not Disturb",
	'online' => "\nChange status to Online",
	'away' => "\nChange status to Away",
	'occ' =>  "\nChange status to Occupied",
	'ffc' =>  "\nChange status to Free For Chat",
	'offline' => "\nDisconnect from server",
	'auth' => "[UIN | nickname]\nGive authorization",
	'r' => "Reply to last received message",
	'a' => "Send a message to the last person you sent a message",
	'w' =>  "\nPrints contactlist",
	'e' =>  "\nPrints contactlist, but not \"Offline\"",
	'history' => "\nhistory UIN|nick [count] shows last 'count' history\nentries for specified UIN. By default count=10",
	'quit' =>  "\nThis command allows you to do something else\nbehind ICQ",
	'help' => "[cmd]\nShow help on command",
	'!' => "OS command\nNote the space after exclamation point",
	'save' => "save config",
	'set' => "[key = ] [value]\nshow/change config options",
	'search' => 'search email@host.domain - Searches for a ICQ user.',
	'wpset' => 'Set basic White Pages info (experimental)',
	'alias' => "[alias = ] [command]\nshow/change command aliases",
	'clear' => "\nClears screen",
	'last' => "UIN | nickname - show last message from UIN or nickname",
	'echo' =>'text - print text',
	'permissions' => " [+|-][web|auth]\nSet 'Web aware/Need authorization' switches",
	'event' => "[command arg]\nView/rule events. Syntax:\n event - view events\n event on <N> - enable event number N\n event off <N> - disable event number N\n event del <N> - delete event number N\n event add <event definition> - add
event",

	'version' => "show vicq version & revision and vICQ.pm version",
	'reg' => "password\nRegister new UIN with specified password"
);

# UINs for micq-style completion
my @uin_history;
my @current_uin_history;



##############################################################################
##
## main part of script
##
##############################################################################

getopts("bot:dDc:r",\%opts);


undef $@;
eval ("require 'Net/vICQ/vICQ.pm'"); # for debug purposes
if($@)
{
	$nomodule++;
	$@ = '';
}
if($nomodule)
{
	eval ("use Net::vICQ");
	if($@)
	{
		print "Cann't find Net::vICQ\n";
		exit;
	}
}
my $module_required = '0.03';
my $module_version;
$module_version = $Net::vICQ::VERSION if defined $Net::vICQ::VERSION;
$module_version = '0.01' unless $module_version;
if($module_version < $module_required)
{
	print "Please, upgrade vICQ.pm to $module_required\n";
	exit;
}
if($opts{'r'})
{
	my $password = getpass("Password for new UIN:");
	print "\n";
	my $err = '';
	($config{'uin'},$err) = register($password);
	if(!$config{'uin'})
	{
		print "Registration failed: $err\n";
		exit;
	} else
	{
		print "Brand new UIN: $config{uin}\n";
	}
}
# Initializing commands
Add_Command_Handler('msg', \&cmd_msg);
Add_Command_Handler('search', \&cmd_search);
Add_Command_Handler('info', \&cmd_info);
Add_Command_Handler('wpset', \&cmd_wpset);
Add_Command_Handler('history', \&cmd_history);
Add_Command_Handler('url', \&cmd_url);
Add_Command_Handler('view', \&cmd_view);
Add_Command_Handler('normal', \&cmd_normal);
Add_Command_Handler('silent', \&cmd_silent);
Add_Command_Handler('sms', \&cmd_sms);
Add_Command_Handler('r', \&cmd_r);
Add_Command_Handler('a', \&cmd_a);
Add_Command_Handler('get', \&cmd_get);
Add_Command_Handler('add', \&cmd_add);
Add_Command_Handler('submit', \&cmd_submit);
Add_Command_Handler('togvis', \&cmd_togvis);
Add_Command_Handler('permissions', \&cmd_permissions);
Add_Command_Handler('clear', \&cmd_clear);
Add_Command_Handler('toginvis', \&cmd_toginvis);
Add_Command_Handler('online', \&cmd_online);
Add_Command_Handler('inv', \&cmd_inv);
Add_Command_Handler('na', \&cmd_na);
Add_Command_Handler('dnd', \&cmd_dnd);
Add_Command_Handler('away', \&cmd_away);
Add_Command_Handler('ffc', \&cmd_ffc);
Add_Command_Handler('occ', \&cmd_occ);
Add_Command_Handler('auth', \&cmd_auth);
Add_Command_Handler('finger', \&cmd_finger);
Add_Command_Handler('w', \&cmd_w);
Add_Command_Handler('e', \&cmd_e);
Add_Command_Handler('quit', \&cmd_quit);
Add_Command_Handler('bd', \&cmd_bd);
Add_Command_Handler('!', \&cmd_shell);
Add_Command_Handler('help', \&cmd_help);
Add_Command_Handler('save', \&cmd_save);
Add_Command_Handler('set', \&cmd_set);
Add_Command_Handler('alias', \&cmd_alias);
Add_Command_Handler('last', \&cmd_last);
Add_Command_Handler('echo', \&cmd_echo);
Add_Command_Handler('offline', \&cmd_offline);
Add_Command_Handler('version', \&cmd_version);
Add_Command_Handler('event', \&cmd_event);
Add_Command_Handler('reg', \&cmd_register);


####################################################

Add_Message_Handler('sms_delivery_receipt',\&sms_delivery_receipt_handler);
Add_Message_Handler('status_change',\&status_change_handler);
Add_Message_Handler('URL',\&url_handler);
Add_Message_Handler('sms_message',\&sms_message_handler);
Add_Message_Handler('contacts_request',\&contacts_request_handler);
Add_Message_Handler('email_message',\&email_message_handler);
Add_Message_Handler('text_message',\&text_message_handler);
Add_Message_Handler('offline_text_message',\&offline_text_message_handler);
Add_Message_Handler('contacts',\&contacts_handler);
Add_Message_Handler('add_message',\&add_message_handler);
Add_Message_Handler('auth_request',\&auth_request_handler);
Add_Message_Handler('sms_response',\&sms_response_handler);
Add_Message_Handler('sms_message',\&sms_message_handler);
Add_Message_Handler('user_short_info',\&user_short_info_handler);
Add_Message_Handler('user_info_not_found',\&user_info_not_found_handler);
Add_Message_Handler('ack_offline',\&ack_offline_handler);
Add_Message_Handler('set_permissions_ack',\&set_permissions_ack_handler);
Add_Message_Handler('user_info_unknown',\&dummy_handler);
Add_Message_Handler('user_info_about',\&user_info_about_handler);
Add_Message_Handler('user_info_main',\&user_info_main_handler);
Add_Message_Handler('user_info_extra_emails',\&user_info_extra_emails_handler);
Add_Message_Handler('set_main_info_ack',\&set_main_info_ack_handler);
Add_Message_Handler('wp_final_result_info',\&wp_final_result_info_handler);
Add_Message_Handler('wp_empty',\&wp_empty_handler);
Add_Message_Handler('user_info_homepage',\&user_info_homepage_handler);
Add_Message_Handler('user_info_work',\&user_info_work_handler);
Add_Message_Handler('wp_result_info',\&wp_result_info_handler);
Add_Message_Handler('user_info_past_background',
		\&user_info_past_background_handler);
Add_Message_Handler('user_info_personal_interests',
		\&user_info_personal_interests_handler);



my $config_file = expand_file($opts{'c'} || '~/.vicq/config');
my @x = keys %contacts;
print &cmd_read;
# &parse_config;

print &cmd_version;
print "\n";

my $icq = Net::vICQ->new($config{uin}, $config{password},"0");
$icq->{_no_lists} = $opts{'b'};
$icq->{_no_offline_messages} = $opts{'o'};
$icq->{_Proxy_Host} = $config{proxy_host};
$icq->{_Proxy_Port} = $config{proxy_port};
$icq->{_Proxy_Login} = $config{proxy_login};
$icq->{_Proxy_Password} = $config{proxy_password};
$icq->{_Requests} = \%requests;
$icq->{_Hide_IP} = 1 if $config{hide_ip};
if($config{https_proxy})
{
	$icq->{_Proxy_Type} = 'https';
	$icq->{_Force_HTTPS_Port}=1 if($config{proxy_force_https_port});
}
elsif($config{socks_proxy})
{
	$icq->{_Proxy_Type} = 'socks';
}

$icq->{_Status} = $config{status} if defined $config{status};


if($opts{'d'}) { $icq->{_Debug} = 1;}
foreach (keys %contacts)
{
	push (@{$icq->{_Auto_Login_Contact_List}}, $_);
}
foreach (keys %contacts)
{
	push (@{$icq->{_Auto_Login_Visible_List}}, $_) if ($prefs{$_}{visible} eq 'yes');
	push (@{$icq->{_Auto_Login_Invisible_List}}, $_) if ($prefs{$_}{invisible} eq 'yes');
}


# Initializing hooks
$icq->Add_Hook("Srv_Mes_Received", \&MessageHandler);
$icq->Add_Hook("Srv_GSC_User_Info", \&DisplayDetails);
$icq->Add_Hook("Srv_Srv_Message", \&MessageHandler);
$icq->Add_Hook("Srv_GSC_MOTD", \&MOTD);
$icq->Add_Hook("Srv_Contact_List", \&ContactList);
$icq->Add_Hook("Srv_BLM_Contact_Online", \&MessageHandler);
$icq->Add_Hook("Srv_BLM_Contact_Offline", \&MessageHandler);

my $message = 0;


if($config{autoconnect})
{
	connect_server();
}

birthday_alert();
$lastactivity = time();
$SIG{INT} = \&disconnect;
# $SIG{PIPE} = sub { print "sigpipe git!\n"; };
$SIG{__DIE__} = \&die_handler;
$SIG{__WARN__} = \&warn_handler;
eval 
{
	$term = new Term::ReadLine::Gnu 'vICQ v0.2';
	$gnu_readline = 1;
	$ignore_start = Term::ReadLine::Gnu::RL_PROMPT_START_IGNORE();
	$ignore_stop = Term::ReadLine::Gnu::RL_PROMPT_END_IGNORE();
	my $attribs=$term->Attribs;
	$attribs->{completer_quote_characters} ='"';
	if ($config{micq_like_completion}){
		print "mICQ style completion\n";
		$term->add_defun('tab',\&tab,ord("\t"));
	}else{
		$attribs->{completion_function}=
		sub {

			my ($text, $line, $start, $end) = @_;
			return (keys %helps) if (substr($line, 0, $start) =~ /^\s*$/);
			return (completion_func($text,(map { $contacts{$_}{name}} keys %contacts, keys %contacts)));
		};
	}
#
# People can enter russian/chinese etc... 
#
	$term->parse_and_bind("set meta-flag on");
	$term->parse_and_bind("set convert-meta off");
	$term->parse_and_bind("set input-meta on");
	$term->parse_and_bind("set output-meta on");
	print "Using GNU readline\n";
};
if ($@) 
{
	$term = new Term::ReadLine 'vICQ v0.2';
	print "Using simplified readline. Install Term::ReadLine::GNU to get more service\n";
}

undef $@;

if ($ENV{TERM}=~/^xterm*/ || $ENV{TERM} eq 'rxvt') {
	print "\033]0;vICQ\a";
}    

while (!$done &&  (defined ($_ = is_interactive() ? $term->readline(make_prompt($config{prompt})) : <STDIN> ))) 
{
	chomp;
	$lastactivity = time();
	my $res = parse_command($_);
	warn $@ if $@;
	if($res) 
	{
		print $res, "\n" unless $@;
	}
	#
	# wierd sleep(2) :(( sleep conflicts with alarm
	#
	my $tout = $opts{'t'} || 2;
	if(!is_interactive())
	{
		my $t = $keepalive;
		while(($t+$tout) > $keepalive) {sleep 1; }
	}
}

alarm 0;
$SIG{INT} = 'DEFAULT';
$SIG{ALRM} = 'DEFAULT';
print "\n" . &save_config unless ($config{keep_config});



###########################################################################
##
## subroutines section
##
###########################################################################


sub parse_options_line
{
	my $s = shift;
	my ($name, $value) = ('','');
	if ($s =~ /([^ \t=]+?)\s*=\s*(.*)/) {
		$name = lc $1;
		$value = $2;
	}
	return ($name,$value)
}

sub parse_alias_line
{
	my $s = shift;
	my $new = '';
	my $old = '';
	if ($s =~ /([^ \t=]+)\s*=\s*([^ \t=]+)/) {
		$new =$1;
		$old = $2;
	}
	return ($new, $old);
}

sub parse_contacts_line
{
	my $s = shift;
	my $visible='no';
	my $invisible='no';
	my $uin = '';
	my $name = '';
	if($s =~ /^([\*~]?\d+)\s+(.*)$/)
	{
		$uin = $1;
		$name = $2;
		if($uin =~ /^\*(\d+)/)
		{
			$uin = $1;
			$visible = 'yes';
			$invisible = 'no';
			
		} 
		elsif ($uin =~ /^~(\d+)/)
		{
			$uin = $1;
			$invisible = 'yes';
			$visible = 'no';
		}
	}
	return ($uin, $name, $visible, $invisible);
}

sub compose_contacts_line
{
	my ($uin,$nick,$visible,$invisible) = @_;
	if($visible  ne 'yes')
	{
		$uin = "~$uin" if($invisible eq 'yes');
	} else
	{
		$uin = "*$uin" if($visible eq 'yes');
	}
	return "$uin $nick";
}


sub compare_events
{
	my ( $hook1, $hook2) = @_;
	if( ($hook1->{eventspec} eq $hook2->{eventspec})
		&& ($hook1->{type} eq $hook2->{type})
		&& ($hook1->{nick} eq $hook2->{nick})
		&& ($hook1->{command} eq $hook2->{command})
		&& ($hook1->{active} eq $hook2->{active})
	) { return 1; }
	return 0;
}

sub getpass
{
	my $prompt = shift;
	system "stty -echo";
	print "$prompt";
	$_ = <>;
	chop;
	system "stty echo";
	return $_;
}

###############
# Subroutine: help
# Purpose: shows help on given command

sub help
{
	my $cmd = shift;
	# my @subnames = keys %subtopics;
	if(in_array($cmd, [keys %subtopics]))
	{
		print "Section: $cmd, use help cmd for command help\n";
		foreach (@{$subtopics{lc $cmd}})
		{
			print "$_\n";
		}
	}
	elsif ($cmd) {
		if (exists $helps{$cmd}) {
		  printf "Format: %s %s\n",$cmd ,$helps{$cmd} ;
		} else {
		  print "Undefined command '$cmd'\n";
		}  
	} else {
		print "Use help [section] for section command list\n";
		foreach (keys %subtopics)
		{
			print "$_ : $descriptions{$_}\n";
		}
	 }  
}

sub save_options
{
	my @opts_files = ();
	my @bad_vars = ();
	my $modified = 0;
	my @lines = ();
	# return '' unless exists $files{options}{file};
	push @opts_files,$files{options}{file} if exists $files{options}{file};
#
# Getting options file
#
	foreach (keys %config) 
	{
		my $file = $opt_info{$_}{file};
		if(($file ne '') && !(in_array($file,\@opts_files)) )
		{
			push @opts_files, $file;
		}
		# print "$_ $opt_info{$_}{file}:$opt_info{$_}{line}\n";
	}
#
# Saving options file by file
#
	foreach my $file (@opts_files)
	{
		open CONF,"< " . $file or die "Cann't read config file $file";
		@lines = <CONF>;
		$modified = 0;
		chomp @lines;
		close CONF;
		foreach (keys %config) 
		{
			next if (($_ eq 'password') &&  !$save_password);
			if($file eq $opt_info{$_}{file}) {
				my ($name,$value) = parse_options_line($lines[$opt_info{$_}{line}]);
				if($name eq $_)
				{
				 	if($value ne $config{$_}) {
						$lines[$opt_info{$_}{line}] = "$_=$config{$_}";
						# print "saving $_ to $opt_info{$_}{file}:$opt_info{$_}{line}\n";
						$modified = 1;
					}
				} else
				{
					# hmmmm who is messing with the files?!?!
					# print "error $_ at $file:$opt_info{$_}{file}:$opt_info{$_}{line}\n";
					print "\nConfig files are changed. Don't mess with files while vICQ running!\n";
					print "Options not saved!\n";
					return;
				}
				
			}
		}

		if($modified)
		{
			open CONF,"> " . $file or die "Cann't write config file $file";
			foreach (@lines)
			{
				print CONF $_,"\n";
			}
			close CONF;
		}
	}
# Now saves all new options
	my $file = '';
	if(exists $files{options}{file})
	{
		$file = $files{options}{file};
		open CONF,"< " . $file or die "Cann't read config file $file";
		@lines = <CONF>;
		chomp @lines;
		close CONF;
		foreach (keys %config) 
		{
			next if (($_ eq 'password') &&  !$save_password);
			if($opt_info{$_}{file} eq '') {
				splice @lines,$files{options}{line}+1,0,"$_=$config{$_}";
				# print "saving new option $_ to $file:$files{options}{line}\n";
				$modified = 1;
			}
		}
	} else
	{
		$file = $config_file;
		open CONF,"< " . $file or die "Cann't read config file $file";
		@lines = <CONF>;
		chomp @lines;
		close CONF;
		my $section_defined = 0;
		foreach (keys %config) 
		{
			next if (($_ eq 'password') &&  !$save_password);
			if($opt_info{$_}{file} eq '') {
				push @lines,'[options]' unless $section_defined;
				$section_defined = 1;
				push @lines,"$_=$config{$_}";
				# print "saving new option $_ to $file:$files{options}{line}\n";
				$modified = 1;
			}
		}
	}
	if($modified)
	{
		open CONF,"> " . $file or die "Cann't write config file $file";
		foreach (@lines)
		{
			print CONF $_,"\n";
		}
		close CONF;
	}

}

sub save_contacts
{
	my @contacts_files = ();
	my @bad_vars = ();
	my $modified = 0;
	my @lines = ();
	push @contacts_files, $files{contacts}{file}  if exists $files{contacts}{file};
#
# Getting contacts file
#
	foreach (keys %contacts) 
	{
		my $file = $contacts_info{$_}{file};
		if(($file ne '') && !(in_array($file,\@contacts_files)) )
		{
			push @contacts_files, $file;
		}
	}
#
# Saving contacts file by file
#
	foreach my $file (@contacts_files)
	{
		open CONF,"< " . $file or die "Cann't read config file $file";
		@lines = <CONF>;
		$modified = 0;
		chomp @lines;
		close CONF;
		foreach (keys %contacts) 
		{
			if($file eq $contacts_info{$_}{file}) {
				my ($uin,$nick,$visible,$invisible) = parse_contacts_line($lines[$contacts_info{$_}{line}]);
				if($uin eq $_)
				{
				 	if(($nick ne $contacts{$_}{name}) 
						|| ($prefs{$_}{visible} ne $visible)
						|| ($prefs{$_}{invisible} ne $invisible) ) {
						$lines[$contacts_info{$_}{line}] = compose_contacts_line($_, $contacts{$_}{name}, $prefs{$_}{visible}, $prefs{$_}{invisible});
						$modified = 1;
					}
				} else
				{
					# hmmmm who is messing with the files?!?!
					# print "error $_ at $file:$opt_info{$_}{file}:$opt_info{$_}{line}\n";
					print "\nConfig file $file have been changed.\n";
					print "Don't mess with files while vICQ running!\n";
					print "Contacts not saved!\n";
					return;
				}
				
			}
		}

		if($modified)
		{
			open CONF,"> " . $file or die "Cann't write config file $file";
			foreach (@lines)
			{
				print CONF $_,"\n";
			}
			close CONF;
		}
	}
# Now saves all new contacts

	my $file = '';
	if(exists $files{contacts}{file})
	{
		$file = $files{contacts}{file};
		open CONF,"< " . $file or die "Cann't read config file $file";
		@lines = <CONF>;
		chomp @lines;
		close CONF;

		foreach (keys %contacts) 
		{
			if($contacts_info{$_}{file} eq '') {
				my $newline = compose_contacts_line($_, $contacts{$_}{name}, 
					$prefs{$_}{visible}, $prefs{$_}{invisible});
				splice @lines,$files{contacts}{line}+1,0,$newline;
				$modified = 1;
	
			}
		}
	} else
	{
		$file = $config_file;
		open CONF,"< " . $file or die "Cann't read config file $file";
		@lines = <CONF>;
		chomp @lines;
		close CONF;

		my $section_defined = 0;
		foreach (keys %contacts) 
		{
			if($contacts_info{$_}{file} eq '') {
				my $newline = compose_contacts_line($_, $contacts{$_}{name}, 
					$prefs{$_}{visible}, $prefs{$_}{invisible});
				push @lines,'[contacts]' unless $section_defined;
				$section_defined = 1;
				push @lines,$newline;
				$modified = 1;
	
			}
		}

	}
	
	if($modified)
	{
		open CONF,"> " . $file or die "Cann't write config file $file";
		foreach (@lines)
		{
			print CONF $_,"\n";
		}
		close CONF;
	}

}

sub save_events
{
	my @events_files = ();
	my $modified = 0;
	my @lines = ();
	push @events_files,$files{events}{file} if  exists $files{events}{file};
#
# Getting events files
#
	foreach (@ExternalHooks) 
	{
		my $file = $_->{file};
		if(($file ne '') && !(in_array($file,\@events_files)) )
		{
			push @events_files, $file;
		}
	}
#
# Replacing aliases file by file
#	
	foreach my $file (@events_files)
	{
		open CONF,"< " . $file or die "Cann't read config file $file";
		@lines = <CONF>;
		$modified = 0;
		chomp @lines;
		close CONF;
		foreach (@ExternalHooks) 
		{
			if($file eq $_->{file}) {
				my $hook = parse_event_definition($lines[$_->{line}]);
				if($hook->{eventspec} eq $_->{eventspec})
				{
				 	if(!compare_events($hook,$_))  {
						$lines[$_->{line}] = compose_event_definition($_);
						$modified = 1;
					}
				} else
				{
					print "\nConfig file $file have been changed.\n";
					print "Don't mess with files while vICQ running!\n";
					print "Events not saved!\n";
					return;
				}
				
			}
		}

#
# Deleting aliases
#
		foreach (@DeletedHooks)
		{
			if($file eq $_->{file}) {
				my $hook = parse_event_definition($lines[$_->{line}]);
				if($hook->{eventspec} eq $_->{eventspec})
				{
					$lines[$_->{line}] = "### TO DELETE ###";
					$modified = 1;
				} else
				{
					# hmmmm who is messing with the files?!?!
					print "\nConfig file $file have been changed.\n";
					print "Don't mess with files while vICQ running!\n";
					print "Events not saved!\n";
					return;
				}
				
			}

		}
		@DeletedHooks = ();
		if($modified)
		{
			open CONF,"> " . $file or die "Cann't write config file $file";
			foreach (@lines)
			{
				print CONF $_,"\n" unless $_ eq '### TO DELETE ###';
			}
			close CONF;
		}


	}

#
# Saving new events
#


	my $file = '';
	if(exists $files{events}{file})
	{
		$file = $files{events}{file};
		open CONF,"< " . $file or die "Cann't read config file $file";
		@lines = <CONF>;
		chomp @lines;
		close CONF;

		foreach (@ExternalHooks) 
		{
			if($_->{file} eq '') {
				splice @lines,$files{aliases}{line}+1,0,
					compose_event_definition($_);
				$modified = 1;

			}
		}
	} else
	{
		$file = $config_file;
		open CONF,"< " . $file or die "Cann't read config file $file";
		@lines = <CONF>;
		chomp @lines;
		close CONF;

		my $section_defined = 0;
		foreach (@ExternalHooks) 
		{
			if($_->{file} eq '') {
				push @lines, compose_event_definition($_);
				push @lines,'[events]' unless $section_defined;
				$section_defined = 1;
				$modified = 1;
			}
		}

	}
	if($modified)
	{
		open CONF,"> " . $file or die "Cann't write config file $file";
		foreach (@lines)
		{
			print CONF $_,"\n";
		}
		close CONF;
	}
}




sub save_aliases 
{
	my @aliases_files = ();
	my $modified = 0;
	my @lines = ();
	push @aliases_files, $files{aliases}{file} if exists $files{aliases}{file};
#
# Getting aliases file
#
	foreach (keys %aliases) 
	{
		my $file = $aliases_info{$_}{file};
		if(($file ne '') && !(in_array($file,\@aliases_files)) )
		{
			push @aliases_files, $file;
		}
	}
#
# Replacing aliases file by file
#	
	foreach my $file (@aliases_files)
	{
		open CONF,"< " . $file or die "Cann't read config file $file";
		@lines = <CONF>;
		$modified = 0;
		chomp @lines;
		close CONF;
		foreach (keys %aliases) 
		{
			if($file eq $aliases_info{$_}{file}) {
				my ($alias, $cmd) = parse_alias_line($lines[$aliases_info{$_}{line}]);
				if($alias eq $_)
				{
				 	if($cmd ne $aliases{$_})  {
						$lines[$aliases_info{$_}{line}] = "$alias=$aliases{$_}";
						$modified = 1;
					}
				} else
				{
					# hmmmm who is messing with the files?!?!
					print "\nConfig file $file have been changed.\n";
					print "Don't mess with files while vICQ running!\n";
					print "Aliases not saved!\n";
					return;
				}
				
			}
		}

#
# Deleting aliases
#
		foreach (keys %deleted_aliases)
		{
			my ($alias, $cmd) = parse_alias_line($lines[$deleted_aliases{$_}{line}]);
			if($alias eq $_)
			{
					$lines[$deleted_aliases{$_}{line}] = "### TO DELETE ###";
					$modified = 1;
			} else
			{
				# hmmmm who is messing with the files?!?!
				print "\nConfig file $file have been changed.\n";
				print "Don't mess with files while vICQ running!\n";
				print "Aliases not saved!\n";
				return;
			}
		}
		%deleted_aliases = ();
		if($modified)
		{
			open CONF,"> " . $file or die "Cann't write config file $file";
			foreach (@lines)
			{
				print CONF $_,"\n" unless $_ eq '### TO DELETE ###';
			}
			close CONF;
		}


	}

#
# Saving new aliases
#


	my $file = '';
	if(exists $files{aliases}{file})
	{
		$file = $files{aliases}{file};
		open CONF,"< " . $file or die "Cann't read config file $file";
		@lines = <CONF>;
		chomp @lines;
		close CONF;

		foreach (keys %aliases) 
		{
			if($aliases_info{$_}{file} eq '') {
				splice @lines,$files{aliases}{line}+1,0,"$_=$aliases{$_}";
				$modified = 1;
	
			}
		}
	} else
	{
		$file = $config_file;
		open CONF,"< " . $file or die "Cann't read config file $file";
		@lines = <CONF>;
		chomp @lines;
		close CONF;

		my $section_defined = 0;
		foreach (keys %aliases) 
		{
			if($aliases_info{$_}{file} eq '') {
				push @lines,"$_=$aliases{$_}";
				push @lines,'[aliases]' unless $section_defined;
				$section_defined = 1;
				$modified = 1;
			}
		}

	}
	
	if($modified)
	{
		open CONF,"> " . $file or die "Cann't write config file $file";
		foreach (@lines)
		{
			print CONF $_,"\n";
		}
		close CONF;
	}



}


sub save_config
{
	my $section = '';
	my $output = '';
	$output .= "Writing config...";
	if(!-e $ENV{'HOME'} . "/.vicq")
	{
		mkdir $ENV{'HOME'} . "/.vicq",0700;
	}
	save_options();
	save_contacts();
	save_aliases();
	save_events();
	$output .= "done\n";
	return $output;
}


sub expand_file
{
	my $file = shift;
	$file =~ s/^~(\w+)?/$1 ? (getpwnam($1))[7] : $ENV{HOME}/eg;
	return $file;
}

sub parse_file
{
	my $file = shift;
	my $uin;
	my $output = '';
	my $sfile =  $file;
	# print "Parsing... $file\n";
	$file = expand_file($file);
	# if($file !~ /^[\.\/]/)
	# {
		# $file = "$ENV{HOME}/.vicq/$file";
	# }
	my $base = &getcwd() . '/';
	if($file =~ /(.*\/)[^\/]*/)
	{
		$base = $1;
	}
	if( -e "$file")
	{
		open CONF,"< $file" or die "Cann't read config file $file";
	} else
	{
		return "Cann't open config file $file\n";
	}
	$file_info{$file}{mtime}= (stat($file))[9];
	$file_info{$file}{modified}= 0;
	my @z = keys %contacts;
	my @lines = <CONF>;
	my $line_number = -1;
	foreach (@lines)
	{
		$line_number++;
		chomp;
		s/^\s+//g;
		s/\s+$//g;
		if($_ eq '') { next; }
		# skip comments;
		if(/^#/) {next;}
		if(/^include\s+(.*)/i)
		{
			my $f = $1;
			if($f !~ /^[\/~]/)
			{
				$f = $base . $f;
			}
			$output .= parse_file($f);
			next;
		}
		if(/^\[(\w+)\]$/)
		{
			$section = lc($1);
			$files{$section}{file} = $file;
			$files{$section}{line} = $line_number;
		} 
		elsif($section eq 'phones')
		{
				$smsphones{$1} = $2 if /^\s*(\w+)\s+(.+)/;
		}
		elsif($section eq 'options')
		{
		      if (/([^ \t=]+?)\s*=\s*(.*)/) {
				my $key=lc($1);
				if(exists $config{$key})
				{
					print "Warning! '$key' redefined in $file:$line_number\n" unless $config_loaded;
				}
				$config{$key} = $2;
				$opt_info{$key}{file} = $file;
				$opt_info{$key}{line} = $line_number;
				
				if ($key eq 'password') {
			   	$save_password=1;
		       }   
			}
		}
		elsif($section eq 'contacts')
		{
			if(/^([\*~]?\d+)\s+(.*)$/)
			{
				$uin = $1;
				my $name = $2;
				my $visible='no';
				my $invisible='no';
				if($uin =~ /^\*(\d+)/)
				{
					$uin = $1;
					$visible = 'yes';
					$invisible = 'no';
					
				} 
				elsif ($uin =~ /^~(\d+)/)
				{
					$uin = $1;
					$invisible = 'yes';
					$visible = 'no';
				}
				$contacts{$uin} = { 
									name 	=> $name,
									aliases => []
								};
				$prefs{$uin} = {
									ip		=> 'x.x.x.x',
									status	=> 'Offline',
									visible => $visible,
									invisible => $invisible
								} unless exists $prefs{$uin};

				$contacts_info{$uin}{file} = $file;
				$contacts_info{$uin}{line} = $line_number;
			} else
			{
				if(($_ ne '') && ($uin ne ''))
				{
					push (@{$contacts{$uin}{aliases}},$_);
				}
			}
			
	

		}
		elsif ($section eq 'events') 
		{
			my $hook=parse_event_definition($_);
			$hook->{file} = $file;
			$hook->{line} = $line_number;
			push @ExternalHooks, $hook if defined $hook;
			if($hook->{eventspec} eq 'Normal') 
			{
				$output .= "\n'Normal' is deprecated, use text_message instead\n";
			}

		}
		elsif ($section eq 'sounds') {
		   if (/^\s*(\S+)\s+(.*$)/) {
		       my $event = $1;
		       my $file = $2;
		       my $type = $event;
		       # convert glob-style notation to regexp
		       $type=~s/([\\.\$])/\\$1/g;
		       $type=~s/\?/\./g;
		       $type=~s/\*/\.*/g;
		       push @SoundHooks,{'eventspec'=>$event,
		       'type'=>$type,'file'=>$file};
		       
		    }  
		} elsif ($section eq 'macroses') {
			if (/([^ \t=]+)\s*=\s*([^ \t=]+)/) {
				my ($cmd,$macro) =  (lc($1),$2);
				if(in_array($cmd,[keys %_command_handlers]))
				{
					$output .=  "Command $cmd  already defined\n";
					next;
				}
				$macroses{$cmd} = $macro;
			}
		} elsif ($section eq 'aliases') {
			if (/([^ \t=]+)\s*=\s*([^ \t=]+)/) {
				my $new =$1;
				my $old = $2;
				die "Cannot alias nonexistent command $old\n"
				if ! exists $_command_handlers{$old};
				$helps{$new} = $helps{$old};		
				$_command_handlers{$new} = $_command_handlers{$old};
				$aliases{$new}=$old;
				$aliases_info{$new}{file} = $file;
				$aliases_info{$new}{line} = $line_number;
		  } else {
		     die "Invalid alias syntax\n"; 
		 }   
		} else {
		   die "Invalid section in config file($file): $section\n";
		    }   
	}
	return $output;
}


sub parse_config
{
	my $section = '';
	my $uin;
	my $output = '';
	@ExternalHooks = ();
	# my $home = $ENV{HOME};
	if(!-e $ENV{'HOME'} . "/.vicq")
	{
		mkdir $ENV{'HOME'} . "/.vicq",0700;
	}

	if( -e $config_file)
	{
		$output .= parse_file($config_file);
	} else
	{
		open CONF,"> $config_file" or die "Cann't create config file $config_file";
	}
	if($config{colors})
	{
		$config{separator_color} = 'BLUE' if ($config{separator_color} eq '');
		$config{separator_title_color} = 'CYAN' if ($config{separator_title_color} eq '');
		$config{message_color} = 'LIGHT_BLUE' if ($config{message_color} eq '');
		$config{uin_color} = 'MAGENTA' if ($config{uin_color} eq '');
		$config{nick_color} = 'MAGENTA' if ($config{nick_color} eq '');
		$config{status_color} = 'YELLOW' if ($config{status_color} eq '');
		$config{time_color} = 'YELLOW' if ($config{time_color} eq '');
	}
	if(! $config{separator_length})
	{
		$config{separator_length} = 70;
	}
	unless($config{mode} eq 'silent')
	{
		$config{mode} = 'normal';
	}

	if($config{encoding} ne 'koi')
	{
		$config{encoding} = 'win' if ($config{encoding} ne 'translit');
	}

	if ($config{uin} eq '')
	{
		print "Use vicq -r if you want register new UIN\n";
		print "UIN #:";
		$_ = <>;
		chop;
		$config{uin} = $_;
	}
	&parse_sms_phonebook if $config{sms_phonebook};
	$config{password} = '' unless exists $config{password};
	if ($config{password} eq '')
	{
		$config{password} = getpass("Password:");;
		print "\n";
	}
	$config{prompt} = "vICQ>" unless $config{'prompt'};
	$config{autoconnect} = 1 if  $config{autoconnect} eq  '';
	
	unless (defined $config{log_type}){
		$config{log_type}='u';
	}elsif ($config{log_type}){
		$config{log_type}.='u' unless ($config{log_type}=~/a/ || $config{log_type}=~/u/);
	}
	
	if ($config{log_path}){
		$config{log_path} = expand_file($config{log_path});
		$config{log_path}.='/' unless $config{log_path}=~/\/$/;
	}else{
		$config{log_path}="$ENV{HOME}/.vicq/history/";
		  }
	unless( -e $config{log_path} || $config{log_path} eq $ENV{HOME})
	{
		mkdir "$config{log_path}",0700 or die "Can't create dir $config{log_path}.$!";
	}

	$config{disable_empty_separators}=0 unless defined $config{disable_empty_separators};

	$config{micq_like_completion}=0 unless defined  $config{micq_like_completion};
	
	$config{autosplit}=0 unless defined $config{autosplit};
	$config_loaded = 1;
	return $output;
}

#
# sub parse_sms_phonebook
#
# Reads and parse phonebook specified by sms_phonebook config parameter.
#
sub parse_sms_phonebook {
	return unless $config{sms_phonebook};
	if(open SMSBOOK, $config{sms_phonebook}) {
		my ($nick,$phone);
		while (<SMSBOOK>) {
			s/[#;].*//;
			next if (1../^\s*\[\s*phones\s*\]/i);
			s/^\s*(.*?)\s*$/$1/;
			next unless $_;
			$smsphones{$1} = $2 if /^(\w+)\s+(.+)/;
		}
	close SMSBOOK;
	} else {
		print "! Error: Can't open sms phomebook $config{sms_phonebook}\n";
	}
}

sub parse_command
{
	my $command = shift;
	$command =~ s/^\s+//g;
	$command =~ s/\s+$//g;
	# print ">>$command<<\n\n";
	#my($cmd,@args) = split /\s+/,$command;
	my($cmd,@args) = tokenize($command);
	$cmd = lc($cmd);
	if($gnu_readline)
	{
		$term->remove_history($term->where_history) if (($term->history_get($term->where_history) eq $command));
	}
	return &{$_command_handlers{$cmd}}(@args) if (exists $_command_handlers{$cmd});
	return "No such command. Use 'help' to get help" if ($cmd ne '');
	return '';
}

sub tick_handler
{
	my $err;
	if($err = $icq->GetError())
	{
		foreach(keys %contacts)
		{
			$prefs{$_}{status} = 'Offline';
			$prefs{$_}{IP} = 'x.x.x.x';
		}
		Error($err);
		return;
	}
	return unless $icq->{_LoggedIn};
	alarm 1;
	unless($keepalive++ % 60)
	{
		$icq->Send_Keep_Alive();
	}
	&do_network if $icq->{_Connected};
}

sub do_network {
	if($icq)
	{
			$icq->Execute_Once();
			# $icq->{_Connected} or &disconnect;
	}
}

sub GetStatus
{
	 my $st = shift;
	 $st &= 0xffff;
	 my $hexst =  sprintf '%04x',$st;
	 return $Net::vICQ::_r_Status_Codes{$hexst};
}

sub GetContact
{
	my $uin = shift;
	my $contact = $uin;
	return $contact unless exists $contacts{$uin};
	return $contact if $uin eq '';
	if($contacts{$uin}{name} ne '')
	{
		$contact = $contacts{$uin}{name};
	}
	return $contact;
}

sub GetPhone
{
	my $nick = shift;
	foreach (keys %smsphones)
	{
		return $smsphones{$_} if(lc($_) eq lc($nick));
		
	}
	return '';
}

sub in_array
{
	my $item = shift;
	my $ref = shift;
	foreach (@{$ref})
	{
		return 1 if (lc $_ eq lc $item)
	}
	return 0;
}

sub GetVersion
{
	my($v) = shift;
	$v = $Net::vICQ::_ICQ_Versions{$v} || 'Unknown client';
	return $v;
}

sub GetUIN
{
	my $contact = shift;
	foreach (keys %contacts)
	{
		return $_ if((lc($contacts{$_}{name}) eq lc($contact)) || in_array($contact, $contacts{$_}{aliases}));
		
	}
	if($contact !~ /^\d+$/)
	{
		return 0;
	} else
	{
		return $contact;
	}
		
}

sub read_message
{
	my $text = '';
	# my $term = new Term::ReadLine 'vICQ v0.2';
	my $prompt = "msg# ";
	print "Input '.' in the empty line to end message or '#' to cancel message\n";
	while (1)
	{
 		$_ = is_interactive() ? $term->readline($prompt) : <STDIN>;
		$lastactivity = time();
		chomp;
		last if($_ eq '.');
		if($_ eq '#') 
		{
			$text = '';
			last;
		}
		$term->remove_history($term->where_history()) if($gnu_readline);
		$text .= "$_\r\n";
	}
	$term->remove_history($term->where_history()) if($gnu_readline);
	$text =~ s/\r\n$//;

	return $text;
}

sub read_description
{
	my $text = '';
	my $prompt = "desc# ";
	while (1)
	{
 		$_ = is_interactive() ? $term->readline($prompt) : <STDIN>;
		chomp;
		$lastactivity = time();
		last if($_ eq '.');
		if($_ eq '#') 
		{
			$text = '#';
			last;
		}
		$text .= "$_\r\n";
		$term->remove_history($term->where_history()) if($gnu_readline);
	}
	$term->remove_history($term->where_history()) if($gnu_readline);
	$text =~ s/\r\n$//;
	if($text eq '') { return '#';}
	return $text;
}

sub read_url
{
	my $text = '';
	my $prompt = "URL: ";
	$text = is_interactive() ? $term->readline($prompt) : <STDIN>;
	$lastactivity = time();
	chomp $text;
	$term->remove_history($term->where_history()) if($gnu_readline);
	return $text;
}





sub disconnect {
	alarm 0;
	$SIG{ALRM} = sub {};
	print &save_config unless ($config{keep_config});
	print "Exiting..\n";
	exit(0);
}

sub MOTD {
	 my($Object, $details) = @_;
	 #the message of the day handler... (don't know why you'd want to catch this.. : )
	 print "MOTD = [$details->{Web_Address}]\n";
	 print "\n";
}


sub DisplayDetails {
	 my($Object, $details) = @_;
	 
	 #very generic handler that just print's out all the bit's of data that get passed to it..
}

sub MessageHandler
{
	my($Object, $details) = @_;
	my $output = Invoke_Message_Handler($details);
	if($output ne '')
	{
		if( ($details->{MessageType} ne 'status_change') &&
			($details->{MessageType} ne 'ack_offline'))
		{
			my $time = color_time($details->{Sent_Time});
			$output = "--[ Sent: $time\n" . $output if($details->{Sent_Time});
			$time = color_time($_ = localtime);
			$output = "--[ Recieved: $time\n" . $output unless ($details->{Sent_Time});
			$output = separator() . $output;
			beep($details->{MessageType},$last_message_from,$details->{text});
		}
		output($output);
		if($details->{MessageType} eq "status_change")
		{
			$details->{text} = $details->{Status};
		}
		invoke_hook($details->{MessageType},$details->{Sender},
					decode($details->{text}));
		if($details->{Sender} && $config{auto_info})
		{
			my $haveit = 0;
			foreach (keys %contacts)
			{
				if($_ eq $details->{Sender})
				{
					$haveit = 1;
				}
			}
			get_wp_info($details->{Sender}) unless $haveit;
		}
		redisplay();
	}
}



sub win2koi
{
	my $s = shift;
	$s =~ tr/\xb3޸/\xa7\xb7\xa6\xb6\xa4\xb4/;
	return $s;
}

sub koi2win
{
	my $s = shift;
	$s =~ tr//\xb3޸/;
	return $s;
}

sub translit
{
	my $s = shift;
	return koi2rus($s) if($config{encoding} eq 'koi');
	return koi2rus(win2koi($s));
}

sub koi2rus
{
	my @str = @_;

	map { tr/\xA3\xB3\xC1-\xD0\xD2-\xD5\xD7-\xDA\xDC\xDF\xE1-\xF0\xF2-\xF5\xF7-\xFA\xFC\xFF/eEabcdefgxijklmnoprstuv`ize'ABCDEFGXIJKLMNOPRSTUV`IZE'/ } @str;
	map { s/\xC0/ju/g  } @str;
	map { s/\xD1/ja/g  } @str;
	map { s/\xD6/zh/g  } @str;
	map { s/\xDB/sh/g  } @str;
	map { s/\xDD/sch/g } @str;
	map { s/\xDE/ch/g  } @str;
	map { s/\xE0/Ju/g  } @str;
	map { s/\xF1/Ja/g  } @str;
	map { s/\xF6/Zh/g  } @str;
	map { s/\xFB/Sh/g  } @str;
	map { s/\xFD/Sch/g } @str;
	map { s/\xFE/Ch/g  } @str;

	return join ('', @str);
}


sub decode
{
	my $s = shift;
	return win2koi($s) if($config{encoding} eq 'koi');
	return translit($s) if($config{encoding} eq 'translit');
	return $s;
}

sub encode
{
	my $s = shift;
	return koi2win($s) if($config{encoding} eq 'koi');
	return $s;
}

sub separator
{
	my $title = shift;
	return if ($config{disable_empty_separators} && $title eq "");
	my $len = int(($config{separator_length} - length($title) - 2) / 2);
	if($title ne '')
	{
		return   $colors{$config{separator_color}}."="x$len." ".$colors{$config{separator_title_color}}.$title.$colors{NORMAL}.$colors{$config{separator_color}}." "."="x$len."$colors{NORMAL}\n";
	} else 
	{
				return $colors{$config{separator_color}}."="x$config{separator_length}."$colors{NORMAL}\n";
	}
}

sub color_time
{
	my $uin = shift;
	return $uin unless $config{colors};
	$uin = $colors{$config{time_color}} . $uin . $colors{NORMAL};
	return $uin;
}



sub color_uin
{
	my $uin = shift;
	return $uin unless $config{colors};
	$uin = $colors{$config{uin_color}} . $uin . $colors{NORMAL};
	return $uin;
}

sub color_nick
{
	my $nick = shift;
	return $nick unless $config{colors};
	$nick = $colors{$config{nick_color}} . $nick . $colors{NORMAL};
	return $nick;
}

sub color_status
{
	my $nick = shift;
	return $nick unless $config{colors};
	$nick = $colors{$config{status_color}} . $nick . $colors{NORMAL};
	return $nick;
}
sub color_message
{
	my $message = shift;
	return $message unless $config{colors};
	$message = $colors{$config{message_color}} . $message . $colors{NORMAL};
	return $message;
}

sub color_their_history
{
	my $message = shift;
	return $message unless $config{colors};
	$message = $colors{$config{their_history_color}} . $message . $colors{NORMAL};
	return $message;
}


sub color_my_history
{
	my $message = shift;
	return $message unless $config{colors};
	$message = $colors{$config{my_history_color}} . $message . $colors{NORMAL};
	return $message;
}




sub color_entries
{
	my ($ref) = shift;
	foreach (@{$ref})
	{
		if(/^> /)
		{
			$_ = &color_their_history($_);
		} else
		{
			$_ = &color_my_history($_);
		}
	
	}
}

sub beep
{
	 my ($event_type,$uin,$text) = @_;


	if($config{player} eq '')
	{
		print "\x07";
	} else
	{
	 	for my $hook (@SoundHooks) 
		{
			if ($event_type=~$hook->{type}) 
			{
		     		my $cmd = $hook->{file};
					$cmd=~s/%([%eutn])/&hook_subst($1,$event_type,$uin,$text)/eg;
				my $cmdline = $config{player};
				$cmdline =~ s/\%f/$cmd/;
					system $cmdline;
		   		return;
			}     
	 	}
		
	}
}


sub log_outgoing_event
{
	my $uin = shift;
	my $message = shift;
	 log_event($uin,$message,'<');	
}



sub log_incoming_event
{
	my $uin = shift;
	my $message = shift;
	log_event($uin,$message,'>');	
}

sub log_event{
	my ($uin,$message,$format) = @_;
	return unless $config{log_type};
	my $now_string = localtime;
	$message =~ s/\033\[[10];3.m//g;
	$uin =~ s/\033\[[10];3.m//g;
	$message =~ s/\033\[.m//g;
	$uin =~ s/\033\[.m//g;

	my $fname=($config{log_type}=~/a/)?'vicq.log':"$uin.log";
	open LOG,">> $config{log_path}$fname" or warn "Can't open file $config{log_path}$fname";
	print LOG "--[$format $now_string\n";
	print LOG $message;
	print LOG "\n" if $config{log_type}=~/n/;
	close LOG;
	if ($config{log_type}=~/l/ && $config{log_type}=~/u/)
	{
		my $name=$contacts{$uin}{name} if exists $contacts{$uin};
		eval {
			my $cwd = getcwd();
			chdir $config{log_path}; 
			symlink "$uin.log","$name.log" unless (-e "$name.log");
			chdir $cwd;
		}
	}
}
#
# Is called when something happens. Searches for external program to
# invoke in @ExternalHooks
#
sub invoke_hook {
	 my ($event_type,$uin,$text) = @_;
	 my $nick=GetContact($uin);
	 my $status=short_status($config{status});
	 #print "TYPE='$event_type' UIN='$uin' TEXT='$text' STATUS='$status'\n";
	 for my $hook (@ExternalHooks) {
		 #print "HOOK=".Dumper($hook)."\n";
		if ($event_type=~/$hook->{type}/i && $nick=~/$hook->{nick}/i && $status=~/$hook->{status}/i && $hook->{active}) {
			foreach my $command (@{$hook->{command}}){
				my $cmdline = $command;
                $qtext = $text;
                if($cmdline =~ /^\s*!/)
                {
                    $ENV{VICQ_MESSAGE} = $qtext;
                    $qtext = '$VICQ_MESSAGE';
                }
				$cmdline=~s/%([%eutnsEUTNS])/&hook_subst($1,$event_type,$uin,$qtext)/eg;
				# print ">> $cmdline\n";
				my $res = parse_command($cmdline);
				print "\n",$res,"\n" if($config{debug_hooks});
			}
			return unless $hook->{nonstop};
		}     

	 }
}    

sub hook_subst {
	my ($specifier,$event,$uin,$text)=@_;
  if ($specifier eq '%') {
		return $specifier;
  } elsif ($specifier eq 'e') {
		return $event;
  } elsif ($specifier eq 'u') {
		return $uin;
  } elsif ($specifier eq 'U') {
		return color_uin($uin);
  } elsif ($specifier eq 'n') {
	  my $nick=GetContact($uin);
	  return $uin unless $nick;
	  return ("$nick");
  } elsif ($specifier eq 'N') {
	  my $nick=GetContact($uin);
	  return $uin unless $nick;
	  return color_nick($nick);
 } elsif ($specifier eq 't') {
	  return "$text";
 } elsif ($specifier eq 'T') {
	  return color_message($text);
 }elsif ($specifier eq 's'){
	 return $prefs{$uin}{status};
 }elsif ($specifier eq 'S'){
	 return color_status($prefs{$uin}{status});
 } else {
	  die "Unknown format specifier $specifier in invoke_hook.";
 }
} 

sub redisplay()
{
	if($gnu_readline)
	{
		# if($redisplay)
		# {
				$term->forced_update_display();
		# }
	}
}


sub do_reply 
{ 
			my @args = @_;
			my $uin = shift @args;
			my $msg = '';
			my ($details);
			if(!$uin)
			{
				return 'You didn\'t have UIN for this operation';
			}
			if ($uin =~ /^sms.(\d+)/) {
			    my $target = $1;
				print "Composing message SMS to $target:\n";
				$msg = read_message();
				$last_message_to = $uin;
				my %details = ( SMS_Dest_Number  => $target,
							MessageType => 'SMS',
							text =>  translit($msg),
							delivery_receipt=>'Yes'								
							
				);
				$icq->Send_Command("Cmd_Srv_Message", \%details);
			 	log_outgoing_event("sms.$target","Send SMS to $target\n$msg\n");
				return "You sent SMS message to $target\n";
			
			}    
			my $nick = GetContact($uin);
			$nick = color_nick($nick);
			if($#args>-1)
			{
				$msg = join ' ',@args;
			} else
			{
				print "Composing message to $nick:\n";
				$msg = read_message();
	
			}
			$last_message_to = $uin;
			if($msg eq '') { return "Message canceled"; }
			my %details = ( uin => $uin,
							MessageType => 'text',
							text =>  encode($msg)
			);
			$icq->Send_Command("Cmd_Send_Message", \%details);

			$nick = GetContact($uin);
			$nick = color_nick($nick);
			log_outgoing_event($uin,"Replied to message from $nick\n$msg\n");
			return "You sent instant message to $nick";
}

sub get_my_info
{
	my %details = ( 
					MessageType => "Self_Info_Request",
	);
	$icq->Send_Command("Cmd_Srv_Message", \%details);
	print "Self info request sent\n";
}

sub set_my_info
{
	my $uin = $config{uin};
	my $dir = $ENV{'HOME'} . "/.vicq/wpinfo";
	if(! -e $dir)
	{
		if(!mkdir ($dir,0700))
		{
			print "Cann't create directory $dir\n";
			return;
		}
	}
	
	my $fname = $dir . "/$uin.nfo";
	if(!-e $fname)
	{
		if(open FILE,"> $fname")
		{
			print FILE "Nickname: \n";
			print FILE "Firstname: \n";
			print FILE "Lastname: \n";
			print FILE "Email: \n";
			close FILE;
		} else

		{
			print "Cann't create file $fname \n";
			return;
		}
	} 
	my $editor = $config{editor} || $ENV{'EDITOR'};
	system("$editor $fname");
	if(open FILE,"< $fname")
	{
		my @lines = <FILE>;
		close FILE;
		chomp @lines;
		foreach (@lines)
		{
			if(/^(\w+):\s+(.*)/)
			{
				$wpinfo{$1} = $2;
				print "$1: $2\n";
			}
		}
		my %details = ( 
					MessageType => "Set_Main_WP_Info",
					_nickname => encode($wpinfo{'Nickname'}),
					_firstname => encode($wpinfo{'Firstname'}),
					_lastname => encode($wpinfo{'Lastname'}),
					_email => encode($wpinfo{'Email'})
		);
		print "Submit this info to ICQ directory(y/n)? ";
		my $c = <>;
		chomp $c;
		$c = lc $c;
		$icq->Send_Command("Cmd_Srv_Message", \%details) if ($c eq 'y');
	} else
	{
		print "Cann't open file $fname\n";
	}
	
}



sub get_wp_info
{
	my @args = @_;
	my $uin = GetUIN($args[0]);
	return if ($uin < 10000);
	if(!$uin) { return "No such nick"; }
	my $nick = GetContact($uin);
	$nick = color_nick($nick);
	my %details = ( 
					MessageType => "Get_WP_Info",
					TargetUIN => $uin
	);
	$requested_uin = $uin;
	$icq->Send_Command("Cmd_Srv_Message", \%details);
	$uin = color_uin($uin);
	print "White pages info request for $uin sent\n";
	log_outgoing_event($uin,"White pages info request for $uin sent\n");
}



sub get_info
{
	my @args = @_;
	my $uin = GetUIN($args[0]);
	return if ($uin == 1020);
	if(!$uin) { return "No such nick"; }
	my $nick = GetContact($uin);
	$nick = color_nick($nick);
	my %details = ( 
					MessageType => "user_short_info_request",
					TargetUIN => $uin
	);
	$requested_uin = $uin;
	$icq->Send_Command("Cmd_Srv_Message", \%details);
	$uin = color_uin($uin);
	print "Info request for $uin sent\n";
	log_outgoing_event($uin,"Info request for $uin sent\n");
}

sub prompt_subst
{
	my ($spec,$uin,$status) = @_;
	my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime(time);
	$min = sprintf('%02d', $min);
	$sec = sprintf('%02d', $sec);
	$hour = sprintf('%02d', $hour);
	return $status if($spec eq 'S');
	return $uin if($spec eq 'U');
	return '%' if($spec eq '%');
	return $sec if($spec eq 's');
	return $min if($spec eq 'm');
	return $hour if($spec eq 'h');
	return $spec;
}

sub short_status
{
	my $status = shift;
	return $Net::vICQ::_Short_Status_Desc{$status};
}

sub make_prompt
{
	my $template = shift;
	my $uin =  color_uin($icq->{_UIN});
	my $status = color_status($icq->{_LoggedIn} ? short_status($config{status}) :  'off');
	$template =~ s/%([%SUnsmhdDM])/&prompt_subst($1,$uin,$status)/eg;
	$template =~ s/(\033.*?m)/$ignore_start$1$ignore_stop/g;
	return $template;
}

sub send_text_message
{
	my ($uin,$msg) = @_;
	$last_message_to = $uin;
	my %details = ( uin => $uin,
					MessageType => 'text',
					text =>  encode($msg)
	);
	add_to_uin_history($uin);
	$icq->Send_Command("Cmd_Send_Message", \%details);
	my $nick = GetContact($uin);
	log_outgoing_event($uin,"Send message to $nick\n$msg\n");
	my $t = $keepalive;
}

sub set_status
{
	my ($status)= shift;
	my ($details);
	if(!&connect_server)
	{
		$details->{Status} = $status;
		$config{status} = $status;
		$icq->Send_Command("Cmd_GSC_Set_Status", $details);
	} 
}

sub send_url_message
{
	my ($uin,$url,$desc) = @_;
	$last_message_to = $uin;
	if($desc eq '#') { return 'URL canceled!'; }
		my %details = ( uin => $uin,
						MessageType => 'url',
						URL =>  encode($url),
						Description => encode($desc)
		);
		add_to_uin_history($uin);
	$icq->Send_Command("Cmd_Send_Message", \%details);
	my $nick = GetContact($uin);
	log_outgoing_event($uin,"Send URL message to $nick\nURL: $url\nDecription:\n$desc\n");

}

sub history
{
	my ($uin, $messages) =  @_;
	return unless $config{log_type};
	$uin =~ s/\033\[[10];3.m//g;
	my $pager = $ENV{'PAGER'} || 'less -R';
	my $home = $ENV{HOME};
	my $fname=($config{log_type}=~/a/)?'vicq.log':"$uin.log";
	if(!(open LOG,"< $config{log_path}$fname"))
	{
		print "No history file for $uin\n";
		return;
	}
	my $messages_count=0;
	while(<LOG>)
	{
		$messages_count++ if(/^--\[/);
	}
	seek LOG,0,'SEEK_SET';
	$messages = $messages_count if($messages < 0);
	while($messages_count > $messages)
	{
		$_ = <LOG>;
		$messages_count-- if(/^--\[/);
	}
	my @lines = <LOG>;
	while(($lines[0] !~ /^--\[/) && @lines)
	{
		shift @lines;
	}
	if(@lines)
	{
		my $data = join '',@lines;
		my @entries = split /--\[/,$data;
		@entries = reverse @entries;
		chomp @entries;
		color_entries(\@entries) if($config{colored_history});
		$uin = color_uin($uin) if($config{colored_history});
		local $SIG{PIPE}= 'IGNORE';
		open PAGER,"| $pager";
		print PAGER "History for $uin\n";
		print PAGER join "\n--------------------------\n",@entries;
		close PAGER;
	}
	close LOG;
}


sub remove_from_visible_list
{
	my $ref = shift;
	my ($details);
	$details->{VisibleList} = $ref;
	$icq->Send_Command("Cmd_BOS_Remove_VisibleList", $details);
}

sub add_to_generic_list
{
	my $ref = shift;
	my ($details);
	$details->{GenericList} = $ref;
	$icq->Send_Command("Cmd_BOS_Add_GenericList", $details);
}



sub add_to_visible_list
{
	my $ref = shift;
	my ($details);
	$details->{VisibleList} = $ref;
	$icq->Send_Command("Cmd_BOS_Add_VisibleList", $details);
}

sub remove_from_invisible_list
{
	my $ref = shift;
	my ($details);
	$details->{InVisibleList} = $ref;
	$icq->Send_Command("Cmd_BOS_Remove_InVisibleList", $details);
}

sub add_to_invisible_list
{
	my $ref = shift;
	my ($details);
	$details->{InVisibleList} = $ref;
	$icq->Send_Command("Cmd_BOS_Add_InVisibleList", $details);
}

sub add_to_contact_list
{
	my ($uin,$nick) = @_;
	my ($details);
	$details->{UIN} = $uin;
	$details->{Nick} = $nick;
	$icq->Send_Command("Cmd_Unk");
	$icq->Send_Command("Cmd_Add_List", $details);
}



sub search_by_email
{
	my ($email) = shift;
	my ($details);
	$details->{_email} = $email;
	$details->{MessageType} = "WP_Full_Request";
	$icq->Send_Command("Cmd_Srv_Message",$details);
	$email = color_nick($email);
	return "Search request for $email sent!";
}

sub save_info
{
	my ($uin,$nick,$firstname,$lname,$email,$bdate) = @_;
	my $dir = $ENV{'HOME'} . "/.vicq/wpinfo";
	if(! -e $dir)
	{
		if(!mkdir ($dir,0700))
		{
			print "Cann't create directory $dir\n";
			return;
		}
	}
	
	my $fname = $dir . "/$uin.nfo";

	if(open FILE,"> $fname")
	{
			print FILE "Nickname: $nick\n";
			print FILE "Firstname: $firstname\n";
			print FILE "Lastname: $lname\n";
			print FILE "Email: $email\n";
			print FILE "Birth date: $bdate\n";
			close FILE;
	} else

	{
			print "Cann't create file $fname \n";
			return;
	}
	my $name = GetContact($uin);
	eval {
		my $cwd = getcwd();
		chdir $dir; 
		symlink "$uin.nfo","$name.log" unless (-e "$name.log");
		chdir $cwd;
	} if ($name ne '');
}

sub wp_field
{
	my ($uin,$field_name) = @_;
	return decode($wp{$uin}->{$field_name});
}

sub get_gender 
{
	my $s = shift;
	return 'Female' if($s==1);
	return 'Male';
}

sub build_wp_info
{
	my $uin = shift;
	
	my $local_nick = color_nick(GetContact($uin) || $uin);
	my $name = wp_field($uin,'Firstname') . ' ' . wp_field($uin,'Lastname');
	my $nick = wp_field($uin,'Nickname');
	my $email = wp_field($uin,'Email');
	my $homepage = wp_field($uin,'Homepage');
	my $bday = wp_field($uin,'Birth_Day') . '/' . wp_field($uin,'Birth_Month'). '/' . wp_field($uin,'Birth_Year');
	my $sex = get_gender(wp_field($uin,'Sex'));
	my $address = wp_field($uin,'Address');
	my $wphone = wp_field($uin,'Company_Phone');
	my $wfax = wp_field($uin,'Company_Fax');
	my $age = wp_field($uin,'Age');
	my $company = wp_field($uin,'Company_Name');
	my $res = "User details for $local_nick [Info summary]\n"
	. "ICQ#: $uin\n"
	. "Name: $name\n"
	. "NickName: $nick\n"
	. "Primary Email: $email\n"
	. "Address: $address\n"
	. "Company: $company\n"
	. "Work Phone: $wphone\n"
	. "Work Fax: $wfax\n"
	. "Gender: $sex\n"
	. "Birth Date: $bday\n"
	. "Age: $age\n";
	save_info($uin,$nick,wp_field($uin,'Firstname'),wp_field($uin,'Lastname'),$email,$bday);
	return $res;
}

sub birthday_alert
{
	my $dir = $ENV{'HOME'} . "/.vicq/wpinfo";
	my ($bdates,@wpfiles,$wpfile,$bdstring,$uin,$nick,$time_left,$d,$m,$y,$foo,$warndays);
	opendir(WPINFO,$dir) or return "Cannot read WP info: $!";
	@wpfiles = grep { /^\d+\.nfo$/ } readdir(WPINFO);
	closedir(WPINFO);

	($foo,$foo,$foo,$d,$m,$y,$foo,$foo,$foo) = localtime(time());
	my $today = POSIX::mktime(0,0,0,$d,$m,$y);

	$warndays = ($config{birthday_warning} or 3);

	for $wpfile (@wpfiles)
	{
		open(WPFILE,"<$dir/$wpfile") or next;
		$bdstring = (grep { /Birth date/ } (<WPFILE>))[0] || '';
		close(WPFILE);
		if ( $bdstring =~ /(\d+)\/(\d+)\/\d+/ )
		{
		    $bdstring = $&;
			$time_left = POSIX::mktime(0,0,0,$1,($2-1),$y)-$today;
			$time_left = $time_left/60/60/24;

			if ($time_left >= 0 && $time_left < $warndays) 
			{
				$wpfile =~ /^\d+/;
				$uin = $&;
				$nick = GetContact($uin);
				print color_nick($nick)." [".color_uin($uin)."] ";
				if ($time_left > 0) 
				{
					if ($time_left > 1) {
					print "will have Birthday in $time_left days (born $bdstring)\n";
					} else { 
					print "will have Birthday tomorrow.\n";
					}
				} else { print "have a Birthday today!\n"; }
			}
		}
	}
}


sub is_interactive
{
	return -t STDIN;
}

sub tab{
	@current_uin_history=@uin_history unless @current_uin_history;
	return unless (@current_uin_history && $gnu_readline);
	my $s=$term->Attribs->{line_buffer};
	if ($s =~ /^\s*(msg\s+.+\/?)\s*$/) 
	{
		my $uin=pop @current_uin_history;
		unshift @current_uin_history, $uin;
		$term->clear_message();
		$term->delete_text(0);
		$term->Attribs->{line_buffer}="msg $uin/";
		$term->Attribs->{end}=length($term->Attribs->{line_buffer});
		$term->Attribs->{point}=$term->Attribs->{end};
		$term->redisplay();
	}
}

sub add_to_uin_history{
	my $uin=GetContact(shift);
	my $i=0;
	foreach (@uin_history){
		if ($uin eq $_){
	 splice(@uin_history,$i,1);
	 last
		}
		$i++;
	}
	push @uin_history, $uin;
	@current_uin_history=@uin_history;
	return "";
}

sub output{
	my $s=join("",@_);
	$s =~ s/\r\n/\n/g;
	print "\033M";
	chomp($s);
	print "\n";
	print "\033[2K";
	print "$s\n";
	return;
}


sub decompose 
{
	 my ($delimexp,$line,$num,$keep,$quotehash,$metaexp,$unmatched) = @_;

	 my $nevermatches = "(?!a)a"; # Anyone have other ideas?
	 if (!defined($delimexp) or $delimexp eq ' ') { $delimexp = '\s+'; }
	 if (!defined($num)) { $num = -1; }
	 if (!defined($keep)) { $keep = 1; }
	 if (!defined($quotehash)) { $quotehash = { "'" => "'", "\"" => "\"" }; }
	 if (!defined($metaexp)) { $metaexp = $nevermatches; }

	 # See if metacharacters has any parenthesized subexpressions:
	 my @matches = ('x' =~ m/$metaexp|(.)/);
	 if (scalar(@matches) > 1) { 
		die "Metacharacter regexp '$metaexp' in decompose may not contain ().";
		return undef;
	 }

	 # Remember if delimexp came with any parenthesized subexpr, and
	 # arrange for it to have exactly one so we know what each piece in
	 # the match below means:

	 my $saveDelimiters = 0;
	 @matches = ('x' =~ m/$delimexp|(.)/);
	 if (scalar(@matches) > 2) {
		die "Delimiter regexp '$delimexp' in decompose may " .
		"contain at most 1 ().";
		return undef;
	 }
	 if (scalar(@matches) == 2) {
		$saveDelimiters = 1;
	 } else {
		$delimexp = "($delimexp)";
	 }

	 my @pieces = ('');
	 my $startNewPiece = 0;
	 my $freshPiece = 1;
	 my $uquote = 0;

	 my %qhash = %{$quotehash};
	 #generate $quoteexp and fix up the closers:
	 my $quoteexp = $nevermatches;
	 for my $opener (keys %qhash) {
		$quoteexp .= '|' . quotemeta($opener);
		 $qhash{$opener} = quotemeta($qhash{$opener});
	 }

	 while ($line) {
		if ($startNewPiece) {
			push @pieces, '';
		    $startNewPiece = 0;
		    $freshPiece = 1;
		 }
		 if (scalar(@pieces) == $num) { last; }
		 # $delimexp is unparenthesized below because we have
		 # already arranged for it to contain exactly one backref ()
		my ($prefix,$delimiter,$quote,$meta,$rest) =
		   ($line =~ m/^((?:[^\\]|\\.)*?)(?:$delimexp|($quoteexp)|($metaexp))(.*)$/s);
		 if (!$keep and defined($prefix)) {
			# remove backslashes in unquoted part:
			$prefix =~ s/\\(.)/$1/g;
		 }
		 if (defined($delimiter)) {
		    $pieces[scalar(@pieces)-1] .= $prefix;
		    if ($saveDelimiters) {
				if (length($pieces[scalar(@pieces)-1]) or !$freshPiece) {
					push @pieces, $delimiter;
				} else {
					$pieces[scalar(@pieces)-1] = $delimiter;
				}
			    $startNewPiece = 1;
		    } elsif (scalar(@pieces) > 1 or $pieces[0]) {
		  	    $startNewPiece = 1;
		    }
		    $line = $rest;
		 } elsif (defined($quote)) {
		    my ($restOfQuote,$remainder) = 
		      ($rest =~ m/^((?:[^\\]|\\.)*?)$qhash{$quote}(.*)$/s);
		    if (defined($restOfQuote)) {
			    if ($keep) {
				    $pieces[scalar(@pieces)-1] .= "$prefix$quote$restOfQuote${$quotehash}{$quote}";
			    } else { #Not keeping, so remove backslash
					     #from backslashed $quote occurrences
					$restOfQuote =~ s/\\$quote/$quote/g;
				    $pieces[scalar(@pieces)-1] .= "$prefix$restOfQuote";
			    }
			    $line = $remainder;
			    $freshPiece = 0;
		    } else { # can't find matching quote, give up
				$uquote = 1;
				last;
		    }
		 } elsif (defined($meta)) {
			$pieces[scalar(@pieces)-1] .= $prefix;
		    if (length($pieces[scalar(@pieces)-1]) or !$freshPiece) {
			    push @pieces, $meta;
		    } else {
			    $pieces[scalar(@pieces)-1] = $meta;
		    }
		    $line = $rest;
		    $startNewPiece = 1;
		 } else { # nothing found, so remainder all one unquoted piece
			if (!$keep and length($line)) {
				$line =~ s/\\(.)/$1/g;
		    }
		    last;
		 }
	 }
	 if (length($line)) { $pieces[scalar(@pieces)-1] .= $line; }
	 if (defined($unmatched)) { ${$unmatched} = $uquote; }
	 return @pieces;
}

sub unquote
{
	my $line = shift;
	$line =~ s/^(['"])(.*)\1$/$2/;
	if($1 eq '"')
	{
		$line =~ s/\\n/\r\n/g;
		$line =~ s/\\"/"/g;
	}
	return $line;
}

sub tokenize {
	my $line= shift;
	my @parts= decompose('(\s+|/|\!|=)',
							$line, undef, 1,undef , '["\']');

	# Walk through parts and combine parenthesized parts properly
	my $nestlevel=0;
	my $tmp='';
	my @tokens= ();
	my $previous_token='';
	my $inquote = 0;
	my $qc = '';
	while( my $tmp= shift @parts) {
		if(!$inquote)
		{
			if(($tmp eq '"') || ($tmp eq "'"))
			{
				push @tokens, $previous_token if($previous_token ne '');
				# print ">> $previous_token\n";
				$previous_token = $tmp;;
				$qc = $tmp;
				$inquote = 1;
			} 
			elsif($tmp =~ /^\s+$/)
			{
				# do nothing
			}
			else
			{
				push @tokens, $previous_token if($previous_token ne '');
				# print "++ $previous_token\n";
				$previous_token = $tmp;
				if($tmp eq '/')
				{
					push @tokens, $tmp;
					push @tokens, (join '',@parts);
					@parts = ();
					$previous_token = '';
				}
			}
		} else
		{
			if($tmp eq $qc)
			{
				$previous_token .= $qc;
				# print "-- $previous_token\n";
				# push @tokens, $previous_token;
				$inquote = 0;
			} else
			{
				$previous_token .= $tmp;
			}
		}
	}
	push @tokens, $previous_token if($previous_token ne '');
	if(@tokens)
	{
		# if($tokens[0] eq '!')
		# {
			# $altargs = join ' ',@tokens[1..$#tokens];
		# } 
		my $first =  $tokens[0];
		($altargs = $line) =~ s/\s*\Q$first\E\s*//;
	}
	foreach (@tokens)
	{	
		$_ = unquote($_);
	}
	return @tokens;
}

sub completion_func
{
	my($text,@candidates) = @_;
	my @r =();

	foreach (@candidates)
	{
		if(s/^(\Q$text\E)/$text/i)
		{
			push @r,$_;
		}
	}
	return @r;
}

sub connect_server
{
	my $err;
	return 0 if($icq->{_Connected});
	print "Trying to connect.";
	$icq->{_Auto_Login} = 1;
	$icq->Connect();
	if(!($err = $icq->GetError()))
	{
		while(!$icq->{_LoggedIn} && !($err = $icq->GetError()))
		{
			$icq->Execute_Once();
			# $icq->{_Connected} or &disconnect;
		}
	}
	if(!$err)
	{
		print "done!\n";
		$SIG{ALRM} = \&tick_handler;
		alarm 1;
		return 0;
	} else
	{
		print "..failed!\n";
		Error($err);
		return -1;
	}

}

sub die_handler
{
	
	# print  "FATAL: $_[0]\n";
	$term->cleanup_after_signal if $gnu_readline;
}

sub warn_handler
{
	return;
}



sub Error
{
	my $s = shift;
	output ("ERROR: $s");
	# redisplay();
}

sub ContactList
{
	print"zz";
}

sub Add_Command_Handler
{
	my($command,$handler) = @_;
	$_command_handlers{$command} = $handler;
}

sub Add_Message_Handler
{
	my($message,$handler) = @_;
	$_message_handlers{$message} = $handler;
}

sub Invoke_Message_Handler
{
	my $details = shift;
	my $message = $details->{MessageType};
	my $data = '';
	return &{$_message_handlers{$message}}($details) if (exists $_message_handlers{$message});
	if($opts{'D'})
	{
		foreach (keys %{$details})
		{
			$data .= "$_ = $details->{$_}\n";
		}
	}
	$data .= "Unknown type: $details->{MessageType} Sender: $details->{Sender}\n";
	return $data;
}

sub register
{
	my $password = shift;
	if($password eq '')
	{
		return (0, "Empty passwords not allowed!");
	}
	my $ricq = Net::vICQ->new("none", $password ,"0");
	$ricq->{_Proxy_Host} = $config{proxy_host};
	$ricq->{_Proxy_Port} = $config{proxy_port};
	$ricq->{_Proxy_Login} = $config{proxy_login};
	$ricq->{_Proxy_Password} = $config{proxy_password};
	if($config{https_proxy})
	{
		$ricq->{_Proxy_Type} = 'https';
		$ricq->{_Force_HTTPS_Port}=1 if($config{proxy_force_https_port});
	}
	elsif($config{socks_proxy})
	{
		$ricq->{_Proxy_Type} = 'socks';
	}
	$ricq->Connect();
	my $uin = $ricq->Register($password);
	my $error = $ricq->{_ErrorStr};
	return ($uin, $error);
}

###################################################3
## Command handlers
##
###################################################

sub cmd_msg 
{
	return $offline_error unless $icq->{_Connected};
	my @args = @_;
	my $uin = '';
	my $msg = '';
	my ($details);
	# if ($arg =~ /^([\*\~\s\w]+)(\/(.*))?$/){

	$uin=GetUIN($args[0]);
	return "No such nick" unless $uin;
	if($args[1] eq '/')
	{
		if ($args[2] ne ''){
		   $msg=$args[2];
		}
	} 
	if($msg eq '')
	{
		   print "Composing message to ".color_nick($args[0]).":\n";
		   $msg = read_message();
	}

	if($msg eq '') { return "Message canceled"; }
	$msg=~s/\\r\\n/\n/g;
	$msg=~s/\\n/\r\n/g;
	my $nick = GetContact($uin);
	$nick = color_nick($nick);
	my $status;
	if ($config{autosplit} && length($msg)>450){
		my $i = 0;
		while(length($msg))
		{
			my $part = substr($msg,0,450);
						substr($msg,0,450) = '';
			send_text_message($uin,$part);
			$i++;
			my $l = length($part);
			my $x = length($msg);
			$status.="You sent part $i of instant message to $nick\n";
		 }
	}else{
		 send_text_message($uin,substr($msg,0,450));
		 $status="You sent instant message to $nick";
	}
	add_to_uin_history($uin);
	chomp($status);
	return $status;
}

sub cmd_search 
{

	return $offline_error unless $icq->{_Connected};
	return search_by_email(@_);
}

sub cmd_info 
{
	return $offline_error unless $icq->{_Connected};
	get_wp_info(@_);
	return '';
}

sub cmd_wpset
{
	return $offline_error unless $icq->{_Connected};
	set_my_info();
	return ''; 
}

sub cmd_history
{
	my @args = @_;
	my $uin = GetUIN($args[0]);
	my $count = $args[1];
	if(!$uin && $config{log_type}=~/u/)
	{
		return 'No such nick';
	}
	if(($count !~ /^\d+$/) && $count)
	{
		return 'Invalid messages count';
	}
	if(!$count) { $count = $config{history_entries}; }
	$count = 20 unless $count;
	history($uin, $count);
	return '';
}

sub cmd_url
{
	return $offline_error unless $icq->{_Connected};
	my @args = @_;
	my $uin = '';
	my $msg = '';
	my ($details);
		$uin = GetUIN($args[0]);
	if(!$uin) { return "No such nick"; }
	my $nick = GetContact($uin);
	$nick = color_nick($nick);
	print "Composing URL message to $nick:\n";
	my $url = '';
	$url = join '',@args[1..$#args] if $#args>0;
	$url = read_url() unless $url;
	if(!$url)
	{
		return "URL message canceled";
	}
	my $desc = read_description();
	if($desc eq '#')
	{
		return "URL message canceled";
	}
	send_url_message($uin,$url,$desc);
	return "You sent URL message to $nick";
}

sub cmd_view
{
	if($last_message_url ne '')
	{
		my $cmd = $config{browser};
		my $urlparam = $last_message_url;
		$urlparam =~ s/'/'\''/g;
		$cmd =~ s/\%u/'$last_message_url'/g;
		system $cmd;
		return '';
	}
	return 'No URL for this moment';
}

sub cmd_silent
{ 
	$config{mode} = 'silent';
	return 'Status change messages disabed';
}

sub cmd_normal
{ 
	$config{mode} = 'normal'; 
	return 'Status change messages enabled';
}

sub cmd_sms
{
	return $offline_error unless $icq->{_Connected};
	my @args = @_;
	my $target = '';
	my $msg = '';
	my ($details);
	if($args[1] eq '/')
	{
		if($args[2])
		{
			$msg = $args[2];
			$target = $args[0];
		}
	} else
	{
				$target = $args[0];
	}
	if ($target !~ /^[-()+0-9]+$/) {
		my $phone;
		if ($phone = GetPhone($target)) {
			print "${target}'s phone number is $phone\n";
			$target = $phone;
		} else {
			return "Can't find ${target}'s phone number in your phonebook";
		}
	}
	if(!$target) { return "invalid number"; }
	unless ($msg) {
		print "Composing message to $args[0]:\n";
		$msg = read_message();
	}

	$target =~ s/[-()]//g;
	$last_message_to = $target;
	if($msg eq '') { return "SMS canceled"; }
	my %details = ( SMS_Dest_Number  => $target,
					MessageType => 'SMS',
					text => translit($msg),
					delivery_receipt=>'Yes'								
	);
	$icq->Send_Command("Cmd_Srv_Message", \%details);
	log_outgoing_event("sms.$target","Send SMS to $target\n$msg\n");
	return "You sent SMS message to $target\n";
}

sub cmd_a
{ 
	return $offline_error unless $icq->{_Connected};
	return do_reply ( $last_message_to, @_); 
}

sub cmd_r
{ 
	return $offline_error unless $icq->{_Connected};
	return do_reply ( $last_message_from, @_); 
}

sub cmd_get
{
	$icq->Send_Command("Cmd_Get_List");
}

sub cmd_add
{
	my @args = @_;
	my $uin = $args[0];
	my $contact = $args[1];
	$contacts{$uin}{name} = $contact;
	$prefs{$uin}{status} = 'Offline';
	$prefs{$uin}{ip} = 'x.x.x.x';
	$prefs{$uin}{connection_type} = 0;
	add_to_contact_list($uin,$contact);
	my ($details);
	my @uins = ($uin);
	$details->{ContactList} = \@uins;
	$icq->Send_Command("Cmd_Add_ContactList", $details) if $icq->{_Connected};
	# parse_command('submit')  if $icq->{_Connected};
	return "You added $uin($contact) to ContactList";
}

sub cmd_submit
{
	return $offline_error unless $icq->{_Connected};
	my ($details);
	my @uins = ();
	foreach (keys %contacts)
	{
		push(@uins,$_);
	}
	$details->{ContactList} = \@uins;
	$icq->Send_Command("Cmd_CTL_UploadList", $details);
	return '';
}

sub cmd_togvis
{
	my @args=@_;
	my $uin = GetUIN($args[0]);
	my @uins = ($uin);
	if($prefs{$uin}{visible} eq 'yes')
	{
		remove_from_visible_list(\@uins) if $icq->{_Connected};
		# add_to_generic_list(\@uins);
		$prefs{$uin}{visible} = 'no';
	} else
	{
		$prefs{$uin}{visible} = 'yes';
		$prefs{$uin}{invisible} = 'no';
		add_to_visible_list(\@uins) if $icq->{_Connected};
	}
	return '';
}

sub cmd_permissions
{
	return $offline_error unless $icq->{_Connected};
	my @args = @_;
	my ($details);
	foreach my $sw (@args)
	{
		$sw = lc $sw;
		if($sw =~ /([\-\+])(\w+)/)
		{
			if($2 eq 'auth')
			{
				$details->{_auth} = $1 eq '+' ?  1 : 0;
			}
			elsif($2 eq 'web')
			{
				$details->{_web} = $1 eq '+' ? 1 : 0;
			} else
			{
				return "Unknown switch: $2";
			}
		} else
		{
			return "$sw - not in switch format";
		}
	}
	$details->{MessageType} = 'Set_Permissions';
	$icq->Send_Command("Cmd_Srv_Message", $details);
	return '';
}

sub cmd_clear
{
	print "\033c";
	return '';
}

sub cmd_toginvis
{
	my @args=@_;
	my $uin = GetUIN($args[0]);
	my @uins = ($uin);
	if($prefs{$uin}{invisible} eq 'yes')
	{
		remove_from_invisible_list(\@uins) if $icq->{_Connected};
		$prefs{$uin}{invisible} = 'no';
	} else
	{
		$prefs{$uin}{invisible} = 'yes';
		$prefs{$uin}{visible} = 'no';
		add_to_invisible_list(\@uins) if $icq->{_Connected};
	}
	return '';
}

sub cmd_inv
{
	set_status("Invisible");
	return $config{status};
}

sub cmd_online
{
	set_status("Online");
	return $config{status};
}

sub cmd_na
{
	set_status("Not_Available");
	return $config{status};
}

sub cmd_dnd
{
	set_status("Do_Not_Disturb");
	return $config{status};
}

sub cmd_away
{
	set_status("Away");
	return $config{status};
}

sub cmd_occ
{
	set_status("Occupied");
	return $config{status};
}

sub cmd_ffc
{
	set_status("Free_For_Chat");
	return $config{status};
}

sub cmd_auth
{
	return $offline_error unless $icq->{_Connected};
	my ($details);
 		$details->{uin} = GetUIN($_[0]);
	$last_message_to = $details->{uin};
 		$icq->Send_Command("Cmd_Authorize", $details);
	return "Authorization sent to $details->{uin}";
}

sub cmd_finger 
{ 
	my $uin=GetUIN($_[0]);
	if (! exists $prefs{$uin}) 
	{
		return color_nick($_[0]). " is not on your contact list\n";
	}
	my $hostname = gethostbyaddr pack("C4", split '\.', $prefs{$uin}{ip}), 2;
	my $connection_type = $prefs{$uin}{connection_type};
	if($connection_type)
	{
		$connection_type =color_status($connection_types{$connection_type});
	}
	return color_nick($_[0]). "(".color_nick($uin).") ".
		color_status($prefs{$uin}{status}) . 
		"\nIP address  : ".
		color_status($prefs{$uin}{ip}).
		"\nHost        : " . 
		color_status($hostname) . 
		"\nDC info     : " . 
		color_status($prefs{$uin}{dc_ip}) .  ':' . 
		color_status($prefs{$uin}{dc_port}) . 
		"\nConn. type  : $connection_type" .
		"\nICQ version : " . 
		color_status(GetVersion($prefs{$uin}{icq_version}));
} 

sub cmd_w
{
	print separator('Offline users');
	foreach ( sort { uc(GetContact($a)) cmp uc(GetContact($b)) } keys %contacts)
	{
		my $ip = sprintf "%15s",$prefs{$_}{ip};
		my $status = $prefs{$_}{status};
		if($status ne 'Offline') { next; }
		$status = color_status($status);
		my $uin = GetContact($_);
		if($prefs{$_}{visible} eq 'yes')
		{
			$uin = "*$uin";
		}
		$uin = sprintf "%20s",$uin;
		$uin = color_nick($uin);
		print "$uin [$ip] ($status)\n";
	}
	print separator('Online users');
	foreach ( sort { uc(GetContact($a)) cmp uc(GetContact($b)) } keys %contacts)
	{
		my $ip = sprintf "%15s",$prefs{$_}{ip};
		my $status = $prefs{$_}{status};
		if($status eq 'Offline') { next; }
		$status = color_status($status);
		my $uin = GetContact($_);
		if($prefs{$_}{visible} eq 'yes')
		{
			$uin = "*$uin";
		}
		elsif($prefs{$_}{invisible} eq 'yes')
		{
			$uin = "~$uin";
		}

		$uin = sprintf "%20s",$uin;
		$uin = color_nick($uin);
		print "$uin [$ip] ($status)\n";
	}
	print separator();
	return '';
}
sub cmd_e
{
	print separator('Online users');
	foreach (sort { uc(GetContact($a)) cmp uc(GetContact($b)) } keys %contacts)
	{
		my $ip = sprintf "%15s",$prefs{$_}{ip};
		my $status = $prefs{$_}{status};
		if($status eq 'Offline') { next; }
		$status = color_status($status);
		my $uin = GetContact($_);
		if($prefs{$_}{visible} eq 'yes')
		{
			$uin = "*$uin";
		}
		elsif($prefs{$_}{invisible} eq 'yes')
		{
			$uin = "~$uin";
		}
		$uin = sprintf "%20s",$uin;
		$uin = color_nick($uin);
		print "$uin [$ip] ($status)\n";
	}
	print separator();
	return '';
}

sub cmd_bd
{
	birthday_alert();
}

sub cmd_quit
{
	$done = 1;
	return "ZZZZZZZZZZZAAAAAAAAAPPPPPPPPP!!!!!";
}

sub cmd_help
{
	my $cmd = shift;
	help($cmd);
	return '';
}

sub cmd_shell
{
	system($altargs);
	return '';
}

sub cmd_save
{
	my $output = '';
	$output .= &save_config;
	$output .=  "Reloading config...";
	$output .= &parse_config;
	$output .= "done";
	return $output;
}

sub cmd_set
{
	my $str=join ('',@_);
	$str=~s/^\s+//;
	$str=~s/\s+$//;
	if ($str eq ''){
		foreach (sort keys %config) 
		{ 
			print "$_ = $config{$_}\n"; 
		}
	}
	elsif($str =~ /(\w+)\s*=\s*(.*)/)
	{
		$config{$1}=$2;
		print "$1 is set to $2\n";
	}
	elsif (exists $config{$str})
	{
		print "$str=$config{$str}\n";
	}
	else
	{
		print "can't find key '$str'\n";
	}
	return "";
}

sub cmd_alias
{
	my $str=join ('',@_);
	$str=~s/^\s+//;
	$str=~s/\s+$//;
	if ($str eq '')
	{
		foreach (sort keys %aliases) { print "$_ = $aliases{$_}\n"; }
	}
	elsif ($str =~ /([^ \t=]+)\s*=\s*([^ \t=]+)?/)
	{
		my ($new,$old)=($1,$2);
		if ($old eq '')
		{
			if (exists $aliases{$new})
			{
				$deleted_aliases{$new}{cmd} = $aliases{$new};
				$deleted_aliases{$new}{file} = $aliases_info{$new}{file};
				$deleted_aliases{$new}{line} = $aliases_info{$new}{line};
				delete $helps{$new};
				delete $_command_handlers{$new};
				delete $aliases{$new};
				delete $aliases_info{$new};
				print "alias for '$new' removed\n";
			}
			else
			{
				print "can't find alias '$str'\n";
			}
		}
		else
		{
			if (! exists $_command_handlers{$old})
			{
				print "Cannot alias nonexistent command $old\n";
				return "";
			}
			$helps{$new} = $helps{$old};		
			$_command_handlers{$new} = $_command_handlers{$old};
			$aliases{$new}=$old;
			$aliases_info{$new}{file} = '' unless exists $aliases_info{$new}{file};
			print "'$new' now is alias for '$old'\n";
		}
	}
	elsif (exists $aliases{$str})
	{
		print "$str=$aliases{$str}\n";
	}
	else
	{
		print "can't find alias '$str'\n";
	}
	return "";
}

sub cmd_last
{
	my $nick=shift;
	my $uin = GetUIN($nick);
	my $unick = GetContact($uin);
	unless ($nick)
	{
		print "format: last UIN | nickname\n";
	}
	elsif (exists $last{$unick})
	{
		print separator()."Last ".$last{$unick}.separator();
	}
	else
	{
		print separator()."no messages from ".color_nick($nick).
			"\n".separator();
	}
	return "";
}

sub cmd_echo
{
	my $output = separator() 
		.  $altargs 
		. "\n" 
		. separator();
	output($output);
	return '';
}

sub cmd_offline
{
	foreach(keys %contacts)
	{
		$prefs{$_}{status} = 'Offline';
		$prefs{$_}{IP} = 'x.x.x.x';
	}
	$icq->Disconnect;
	return '';
}

sub cmd_event
{
	if ($altargs eq '')
	{
		#my $output=separator()."  |A|S|      event       |  nick  |status|    command\n".
		#"-------------------------------------------------------\n";
		my $output=separator()."  |A|      event       |  nick  |status|    command\n".
		"-----------------------------------------------------\n";
		my $i=0;
		foreach my $hook (@ExternalHooks)
		{
			$output.=sprintf("%2d|%1s|%18s|%8s|%6s|%s",++$i, ($hook->{active})?'*':' ', 
				#($hook->{stop})?'*':' ',
				substr($hook->{eventspec},0,17),
				substr($hook->{orig_nick},0,7),
				short_status($hook->{status}) ? substr(short_status($hook->{orig_status}),0,5) : substr($hook->{orig_status},0,5),
				'"'.join('","',@{$hook->{command}}).'"'."\n"
				);
		}
		$output.=separator();
		output($output);
	}
	else
	{
		if ($altargs =~ /(\w+)\s+(.+)/){
			my ($cmd, $arg)=($1,$2);
			if ($cmd eq 'on' && $arg =~ /^\d+$/)
			{
				$ExternalHooks[$arg-1]->{active}=1;
	 		}
			elsif($cmd eq 'off' && $arg =~ /^\d+$/)
			{
				$ExternalHooks[$arg-1]->{active}=0;
			} 
			elsif($cmd eq 'del' && $arg =~ /^\d+$/)
			{
				push @DeletedHooks,$ExternalHooks[$arg-1];
				splice @ExternalHooks, $arg-1,1;
			}
			elsif($cmd eq 'add'){
				my $hook=parse_event_definition($arg);
				if (defined $hook)
				{
					push @ExternalHooks, $hook;
		 		}
				else
				{
					print "Error in event definition. See README.\n";
				}
	 		}
			else
			{
				print "Unknown event command format. See 'help event'\n";
			}
		}
		else
		{
			print "Unknown event command format. See 'help event'\n";
		}
	}
	return '';
}

sub cmd_version 
{
	my $data = '';
	if($Id =~ / (\d+\.\d+) /)
	{
		my $rev = $1;
		my $version = '0.4.1';
		if($config{colors})
		{
			$version = $colors{YELLOW} . $version . $colors{NORMAL};
			$rev = $colors{YELLOW} . $rev . $colors{NORMAL};
		}
		$data .= "vICQ version: [$version] revision:[$rev]\n";
	}
	my $mv = $config{colors} ? $colors{YELLOW} . $module_version . $colors{NORMAL} : $module_version;
	$data .= "vICQ.pm version: [$mv]";
	return $data;

}


sub cmd_read
{
	my $output = "Reading config....";
	$output .= parse_config();
	$output .="done\n";
	return $output;
}

sub cmd_register
{
	my $password = shift;
	#
	# Output directly to screen and wait... wait... wait... :(
	# I WANT EVENT-DRIVEN MODEL IN PERL!!!!!
	#
	print "Registering new UIN, wait please....\n";
	my ($uin,$error) = register($password);
	return $error unless $uin;
	# $registered_uin = $uin;
	return "Brand new UIN: $uin";
}

#######################################################################
## Incomming messages handlers
##
#######################################################################

sub offline_text_message_handler
{
	my $details = shift;
	my $name = color_nick(GetContact($details->{Sender}));
	$last_message_from = $details->{Sender};
	add_to_uin_history($last_message_from);
	my $text = decode($details->{text});
	$text = color_message($text);
	my $data = "Instant offline message from $name:\n$text\n";
	$last{GetContact($details->{Sender})}= $data;
	log_incoming_event($last_message_from, $data);
	return  $data;
	
}


sub text_message_handler
{
	my $details = shift;
	my $name = color_nick(GetContact($details->{Sender}));
	$last_message_from = $details->{Sender};
	add_to_uin_history($last_message_from);
	my $text = color_message(decode($details->{text}));
	my $data = "Instant message from $name:\n$text\n";
	$last{GetContact($details->{Sender})} = $data;
	log_incoming_event($details->{Sender},$data);
	return $data;
}

sub email_message_handler
{
	my $details = shift;
	my $name = color_nick($details->{Name});
	my $email = color_nick($details->{Email});
	my $message = color_message($details->{Text});
	my $data = "Mail from $name <$email>\n$message";
	log_incoming_event($details->{Sender},$data);
	return $data;
}

sub contacts_request_handler
{
	my $details = shift;
	my $name = GetContact($details->{Sender});
	$last_message_from = $details->{Sender};
	my $reason = decode(GetContact($details->{Reason}));
	$name = color_nick($name);
	$reason = color_message($reason);
	my $data = "Contacts request from $name:\n$reason\n";	
	log_incoming_event($last_message_from, $data);
	return $data;
}

sub url_handler
{
	my $details = shift;
	my $name = color_nick(GetContact($details->{Sender}));
	$last_message_from = $details->{Sender};
	$last_message_url = $details->{URL};
	add_to_uin_history($last_message_from);
	$details->{URL} = color_message(decode($details->{URL}));
	$details->{Description} = color_message(decode($details->{Description}));
	my $data = "URL message from $name\nURL : $details->{URL}\nDescription:\n$details->{Description}\n";	
	$last{GetContact($details->{Sender})}=$data;
	log_incoming_event($last_message_from, $data);
	return $data;
}

sub sms_response_handler
{
	my $details = shift;
	my $data = '';
	if($details->{deliverable} ne 'SMTP')
	{
		$data = "Response from SMS server:\n" . 
				"Deliverable  : $details->{deliverable}\n" .
				"Network      : $details->{network}\n" .
				"MessageID    : '$details->{message_id}'\n";
		my $sender=$1 if $details->{message_id}=~/-(\d+)\s*$/;
		$sender ||="SMSC";
		log_incoming_event( "sms.$sender", $data);
	} else
	{

		$data = "Response from SMS server:\n" .
				"Deliverable : via SMTP\n" . 
				"To          : $details->{to}\n";
		 my $sender=$1 if $details->{message_id}=~/-(\d+)\s*$/;
		 $sender ||="SMSC";
		log_incoming_event("sms.$sender",$data);
	}
	return $data;

}

sub auth_request_handler
{
	my $details = shift;
	my $nick = color_nick(decode(GetContact($details->{nick})));
	my $first_name = color_nick(decode($details->{first_name}));
	my $last_name = color_nick(decode($details->{last_name}));
	my $reason = color_message(decode($details->{reason}));
	my $last_message_from = $details->{Sender};
	my $email = decode($details->{mail});
	add_to_uin_history($last_message_from);
	my $Sender = color_uin(GetContact($details->{Sender}));
	my $data = "Authorization request from $Sender\n" .
	"Nick       :     $nick\n" .
	"First name :     $first_name\n" .
	"Last name  :     $last_name\n" .
	"Email      :     $email\n" .
	"\n$reason\n";
	log_incoming_event( $details->{Sender},$data);
	return $data;

}

sub add_message_handler
{
	my $details = shift;
	my $Sender = color_nick(GetContact($details->{Sender}));
	return "You have been added to contact list by $Sender\n";
}

sub contacts_handler
{
	my $details = shift;
	my $nick = color_nick(GetContact($details->{Sender}));
	$last_message_from = $details->{Sender};
	add_to_uin_history($last_message_from);
	my $data = "Got $details->{Count} contacts from $nick:";
	my $i = 0;
	while($i<= $#{$details->{Contacts}})
	{
		$data .= "\n" . color_uin($details->{Contacts}->[$i]) . " " .  color_nick("$details->{Contacts}->[$i+1]");
		$i+=2;
	}
	log_incoming_event($last_message_from, $data);
	return $data;

}

sub user_short_info_handler
{
	my $details = shift;
	my ($nick, $fname, $lname, $email) =
		(	$details->{Nickname} ,
			$details->{Firstname} ,
			$details->{Lastname} ,
			$details->{Email}   
		);
	$nick = color_nick(decode($nick));
	$fname = color_nick(decode($fname));
	$lname = color_nick(decode($lname));
	my $uin = color_uin("#$requested_uin");

	my $data = "$uin info:\nNickname   : $nick\n" 
			. "First name : $fname\n"
			. "Last name  : $lname\n"
			. "Email      : $email\n";
	log_incoming_event($requested_uin,$data);
	return $data;
}
	
sub sms_message_handler
{
	my $details = shift;
	$last_message_from = "sms.$details->{sender}";
	my $data = "SMS message from $details->{sender}($details->{senders_network})\n"
			. "$details->{time}\n"
			. "$details->{text}\n";
	log_incoming_event($last_message_from, $data);
	return $data;
}

sub sms_delivery_receipt_handler
{
	my $details = shift;
	my $data = qq~
SMS delivery receipt
Destination : $details->{destination}
MessageId   : $details->{message_id}
Delivered   : $details->{delivered}
Submitted   : $details->{submition_time}
Delivered   : $details->{delivery_time}
~;
		
	log_incoming_event("sms.$details->{destination}",$data);
	return $data;
}

sub wp_result_info_handler
{
	my $details = shift;
	my $uin = color_uin(deocde($details->{UIN}));
	my $nick = color_nick(decode($details->{Nickname}));
	my $fname = decode($details->{Firstname});
	my $lname = decode($details->{Lastname});
	my $email = decode($details->{Email});
	$search_result .= qq~UIN       : $uin
Firstname : $fname
Nickname  : $nick
Lastname  : $lname
Email     : $email
~;
	return '';
}

sub ack_offline_handler
{
	my $data = "End of offline messages!\n";
	return $data;
}


sub user_info_not_found_handler
{
	my $details = shift;
	my $uin = color_uin("#$requested_uin");
	return "Info for $uin not found\n";
}

sub user_info_homepage_handler
{
	my $details = shift;
	my $uin = $requests{$details->{Ref}};
	foreach ('Birth_Year','Sex','Birth_Day','Language1','Language2',
		'Language3','Birth_Month','Age','Homepage')
	{
		$wp{$uin}->{$_} = $details->{$_};
	}
	return '';
}

sub wp_final_result_info_handler
{
	my $details = shift;
	my $data = '';
	if(!$details->{UIN})
	{
		$data .= "Nothing was found :(\n"
	} else
	{
		my $uin = color_uin($details->{UIN});
		my $nick = color_nick(decode($details->{Nickname}));
		my $fname = decode($details->{Firstname});
		my $lname = decode($details->{Lastname});
	my $email = $details->{Email};
	$data .= $search_result . qq~UIN       : $uin
Firstname : $fname
Nickname  : $nick
Lastname  : $lname
Email     : $email
~;
	}
	$data .= "All users found\n";
	$search_result = '';
	return $data;
}

sub wp_empty_handler
{
	return "No UINs found";
}

sub set_main_info_ack_handler
{
	return "Main info set\n";
}

sub user_info_extra_emails_handler
{
	my $details = shift;
	my $uin = $requests{$details->{Ref}};
	foreach ('Extra_Email_Count','Extra_Emails')
	{
		$wp{$uin}->{$_} = $details->{$_};
	}
	return '';
}

sub user_info_main_handler
{
	my $details = shift;
	my $uin = $requests{$details->{Ref}};
	foreach ( 'Nickname', 'Firstname', 'Lastname',
				'Email',  'City',  'State', 'Telephone',
				'Fax_Num', 'Address', 'Mobile_Phone',
				'Zip', 'GMT_Code' )
	{
		$wp{$uin}->{$_} = $details->{$_};
	}
	return '';
}

sub user_info_about_handler
{
	my $details = shift;
	my $uin = $requests{$details->{Ref}};
	$wp{$uin}->{about} = $details->{about};
	return '';
}

sub user_info_work_handler
{
	my $details = shift;
	my $uin = $requests{$details->{Ref}};
	foreach ('Company_City', 'Company_State', 'Company_Address',
		'Company_Zip', 'Company_Country', 'Company_Name',
		'Company_Department', 'Company_Position', 'Company_Occupation',
		'Company_URL', 'Company_Phone', 'Company_Fax')
	{
		$wp{$uin}->{$_} = $details->{$_};
	}
	return '';
}

sub user_info_personal_interests_handler
{
	my $details = shift;
	my $uin = $requests{$details->{Ref}};
	foreach ('Interests_Count', 'Interests_Type', 'Interests_Desc')
	{
		$wp{$uin}->{$_} = $details->{$_};
	}
}

sub user_info_past_background_handler
{
	my $details = shift;
	my $uin = $requests{$details->{Ref}};
	return build_wp_info($uin);
}

sub set_permissions_ack_handler
{
	return "Web/Auth options set";
}

sub status_change_handler
{
	my $details = shift;
	my $data = '';
	my $status = $details->{Status};
	# exists $details->{Status} ? GetStatus($details->{Status}) : 'Offline';
	# foreach (keys %$details)
	# {
		# print "$_ = $details->{$_}\n";
	# }
	my $ip = $details->{Ip_Address} || 'x.x.x.x';
	my $contact = GetContact($details->{Sender});
	my $oldstatus = 'Offline';
	$oldstatus=$prefs{$details->{Sender}}{status} if exists $prefs{$details->{Sender}}{status};
	my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime(time);
	$min=$min>9?$min:"0$min";
	$mday=$mday>9?$mday:"0$mday";
	$hour=$hour>9?$hour:"0$hour";
	$sec=$sec>9?$sec:"0$sec";
	if(($prefs{$details->{Sender}}{ip} eq 'x.x.x.x') || ($status eq 'Offline'))
	{
		$prefs{$details->{Sender}}{ip} = $ip;
		$prefs{$details->{Sender}}{dc_ip} = $details->{'LAN_IP'};
		$prefs{$details->{Sender}}{dc_port} = $details->{'LAN_Port'};
		$prefs{$details->{Sender}}{icq_version} = $details->{'ICQ_Version'};
		$prefs{$details->{Sender}}{connection_type} = $details->{'ConnectionType'};
	}
	$prefs{$details->{Sender}}{status} = $status;
	if($prefs{$details->{Sender}}{status} ne $oldstatus)
	{
		unless($config{'mode'} eq 'silent')
		{
			my $cstatus = color_status($status);
			$data = color_time("[$mday $hour:$min:$sec] ") . color_nick($contact) . " changed status to $cstatus";
			log_incoming_event($details->{Sender},"$contact change status to $cstatus\n") if $config{log_type}=~/s/;
		}
	}
	return $data;
}

sub parse_event_definition{
	my $cmd=shift;
	my $hook;
	if ($cmd =~ /^\s*(\!)?(\|)?(\S+?)(\/\S+?)?(\|\S+?)?\s+(.+)$/) {
		my $active = 1;
		$active=($1 eq '!')?0:1 if (defined $1);
		my $nonstop=0;
		$nonstop=($2 eq '|')?1:0 if (defined $2);
		my $event = $3;
		my $orig_nick = defined $4 ? $4 : '';
		my $orig_status = defined $5 ? $5 : '';
		my $nick = ($4)?substr($4,1):'*';
		my $status = ($5)?substr($5,1):'*';
		my $command = $6;
		## parse commands
		my @commands=();
		my $cmd='';
		while ($command ne ''){
			if ($command =~ /^\s*\".*?\"\s*$/){
				if ($command =~ s/^(\s*\"(.*?)\"\s*\,?)//){
					$cmd.=$2;
					#warn "1='$1' 2='$2' cmd='$cmd' command='$command'";
					unless ($cmd =~ s/\\$/"/){
					#warn "CMD=$cmd";
					push @commands, $cmd if $cmd;
					$cmd = '';
					}else{
						$command='"'.$command;
					}
				}
			}else{
				$command =~ s/^\s+//;
				push @commands, $command;
				$command ='';
			}
		}
               ## end of parsing commands
		$orig_nick =~ s/^\///g;
		my $type = $event;
		# convert glob-style notation to regexp
		$type=~s/([\\.\$])/\\$1/g;
		$type=~s/\?/\./g;
		$type=~s/\*/\.*/g;

		$nick=~s/([\\.\$])/\\$1/g;
		$nick=~s/\?/\./g;
		$nick=~s/\*/\.*/g;

		$status=~s/([\\.\$])/\\$1/g;
		$status=~s/\?/\./g;
		$status=~s/\*/\.*/g;

		#print 'type="'.$type.'" nick="'.$nick.'" status=."'.$status.'"';
		$hook = { 
				'eventspec'=>$event,
				'nonstop'=>$nonstop,
	 			'type'=>$type,
				'command'=>\@commands,
				'nick'=>$nick, 
				'status'=>$status,
				'active'=>$active,
				'orig_nick'=>$orig_nick,
				'orig_status'=>$orig_status,
				'file'=>'',
				'line'=>''
		};
	}
	return $hook;
}


sub compose_event_definition
{
	my $hook = shift;
	my $definition = '';
	$definition = '!' unless $hook->{active};
	$definition .= $hook->{eventspec};
	$definition .= "/$hook->{orig_nick}" if $hook->{orig_nick} ne '';
	$definition .= "|$hook->{orig_status}" if $hook->{orig_status} ne '';
	if($#{$hook->{command}})
	{
		my @cmds = ();
		my $cmd;
		foreach $cmd (@{$hook->{command}})
		{
			 $cmd =~ s/"/\\"/g;
			push @cmds,$cmd;
		}
		$definition .= " \"" . join('","',@cmds) . '"';
	} else
	{
		$definition .= " " . $hook->{command}[0];
	}
	return $definition;
}

sub dummy_handler
{
	return '';
}

