package FS::SelfService;

use strict;
use vars qw($VERSION @ISA @EXPORT_OK $DEBUG $dir $socket %autoload $tag);
use Exporter;
use Socket;
use FileHandle;
#use IO::Handle;
use IO::Select;
use Storable 2.09 qw(nstore_fd fd_retrieve);

$VERSION = '0.03';

@ISA = qw( Exporter );

$DEBUG = 0;

$dir = "/usr/local/freeside";
$socket =  "$dir/selfservice_socket";
$socket .= '.'.$tag if defined $tag && length($tag);

#maybe should ask ClientAPI for this list
%autoload = (
  'passwd'                    => 'passwd/passwd',
  'chfn'                      => 'passwd/passwd',
  'chsh'                      => 'passwd/passwd',
  'login'                     => 'MyAccount/login',
  'logout'                    => 'MyAccount/logout',
  'customer_info'             => 'MyAccount/customer_info',
  'edit_info'                 => 'MyAccount/edit_info',     #add to ss cgi!
  'invoice'                   => 'MyAccount/invoice',
  'invoice_logo'              => 'MyAccount/invoice_logo',
  'list_invoices'             => 'MyAccount/list_invoices', #?
  'cancel'                    => 'MyAccount/cancel',        #add to ss cgi!
  'payment_info'              => 'MyAccount/payment_info',
  'process_payment'           => 'MyAccount/process_payment',
  'process_payment_order_pkg' => 'MyAccount/process_payment_order_pkg',
  'process_prepay'            => 'MyAccount/process_prepay',
  'list_pkgs'                 => 'MyAccount/list_pkgs',     #add to ss (added?)
  'list_svcs'                 => 'MyAccount/list_svcs',     #add to ss (added?)
  'list_svc_usage'            => 'MyAccount/list_svc_usage',   
  'list_support_usage'        => 'MyAccount/list_support_usage',   
  'order_pkg'                 => 'MyAccount/order_pkg',     #add to ss cgi!
  'change_pkg'                => 'MyAccount/change_pkg', 
  'order_recharge'            => 'MyAccount/order_recharge',
  'cancel_pkg'                => 'MyAccount/cancel_pkg',    #add to ss cgi!
  'charge'                    => 'MyAccount/charge',        #?
  'part_svc_info'             => 'MyAccount/part_svc_info',
  'provision_acct'            => 'MyAccount/provision_acct',
  'provision_external'        => 'MyAccount/provision_external',
  'unprovision_svc'           => 'MyAccount/unprovision_svc',
  'myaccount_passwd'          => 'MyAccount/myaccount_passwd',
  'signup_info'               => 'Signup/signup_info',
  'domain_select_hash'        => 'Signup/domain_select_hash',  # expose?
  'new_customer'              => 'Signup/new_customer',
  'agent_login'               => 'Agent/agent_login',
  'agent_logout'              => 'Agent/agent_logout',
  'agent_info'                => 'Agent/agent_info',
  'agent_list_customers'      => 'Agent/agent_list_customers',
  'mason_comp'                => 'MasonComponent/mason_comp',
);
@EXPORT_OK = ( keys(%autoload), qw( regionselector expselect popselector domainselector didselector) );

$ENV{'PATH'} ='/usr/bin:/usr/ucb:/bin';
$ENV{'SHELL'} = '/bin/sh';
$ENV{'IFS'} = " \t\n";
$ENV{'CDPATH'} = '';
$ENV{'ENV'} = '';
$ENV{'BASH_ENV'} = '';

my $freeside_uid = scalar(getpwnam('freeside'));
die "not running as the freeside user\n" if $> != $freeside_uid;

-e $dir or die "FATAL: $dir doesn't exist!";
-d $dir or die "FATAL: $dir isn't a directory!";
-r $dir or die "FATAL: Can't read $dir as freeside user!";
-x $dir or die "FATAL: $dir not searchable (executable) as freeside user!";

foreach my $autoload ( keys %autoload ) {

  my $eval =
  "sub $autoload { ". '
                   my $param;
                   if ( ref($_[0]) ) {
                     $param = shift;
                   } else {
                     #warn scalar(@_). ": ". join(" / ", @_);
                     $param = { @_ };
                   }

                   $param->{_packet} = \''. $autoload{$autoload}. '\';

                   simple_packet($param);
                 }';

  eval $eval;
  die $@ if $@;

}

sub simple_packet {
  my $packet = shift;
  warn "sending ". $packet->{_packet}. " to server"
    if $DEBUG;
  socket(SOCK, PF_UNIX, SOCK_STREAM, 0) or die "socket: $!";
  connect(SOCK, sockaddr_un($socket)) or die "connect to $socket: $!";
  nstore_fd($packet, \*SOCK) or die "can't send packet: $!";
  SOCK->flush;

  #shoudl trap: Magic number checking on storable file failed at blib/lib/Storable.pm (autosplit into blib/lib/auto/Storable/fd_retrieve.al) line 337, at /usr/local/share/perl/5.6.1/FS/SelfService.pm line 71

  #block until there is a message on socket
#  my $w = new IO::Select;
#  $w->add(\*SOCK);
#  my @wait = $w->can_read;

  warn "reading message from server"
    if $DEBUG;

  my $return = fd_retrieve(\*SOCK) or die "error reading result: $!";
  die $return->{'_error'} if defined $return->{_error} && $return->{_error};

  warn "returning message to client"
    if $DEBUG;

  $return;
}

=head1 NAME

FS::SelfService - Freeside self-service API

=head1 SYNOPSIS

  # password and shell account changes
  use FS::SelfService qw(passwd chfn chsh);

  # "my account" functionality
  use FS::SelfService qw( login customer_info invoice cancel payment_info process_payment );

  my $rv = login( { 'username' => $username,
                    'domain'   => $domain,
                    'password' => $password,
                  }
                );

  if ( $rv->{'error'} ) {
    #handle login error...
  } else {
    #successful login
    my $session_id = $rv->{'session_id'};
  }

  my $customer_info = customer_info( { 'session_id' => $session_id } );

  #payment_info and process_payment are available in 1.5+ only
  my $payment_info = payment_info( { 'session_id' => $session_id } );

  #!!! process_payment example

  #!!! list_pkgs example

  #!!! order_pkg example

  #!!! cancel_pkg example

  # signup functionality
  use FS::SelfService qw( signup_info new_customer );

  my $signup_info = signup_info;

  $rv = new_customer( {
                        'first'            => $first,
                        'last'             => $last,
                        'company'          => $company,
                        'address1'         => $address1,
                        'address2'         => $address2,
                        'city'             => $city,
                        'state'            => $state,
                        'zip'              => $zip,
                        'country'          => $country,
                        'daytime'          => $daytime,
                        'night'            => $night,
                        'fax'              => $fax,
                        'payby'            => $payby,
                        'payinfo'          => $payinfo,
                        'paycvv'           => $paycvv,
                        'paystart_month'   => $paystart_month
                        'paystart_year'    => $paystart_year,
                        'payissue'         => $payissue,
                        'payip'            => $payip
                        'paydate'          => $paydate,
                        'payname'          => $payname,
                        'invoicing_list'   => $invoicing_list,
                        'referral_custnum' => $referral_custnum,
                        'agentnum'         => $agentnum,
                        'pkgpart'          => $pkgpart,

                        'username'         => $username,
                        '_password'        => $password,
                        'popnum'           => $popnum,
                        #OR
                        'countrycode'      => 1,
                        'phonenum'         => $phonenum,
                        'pin'              => $pin,
                      }
                    );
  
  my $error = $rv->{'error'};
  if ( $error eq '_decline' ) {
    print_decline();
  } elsif ( $error ) {
    reprint_signup();
  } else {
    print_success();
  }

=head1 DESCRIPTION

Use this API to implement your own client "self-service" module.

If you just want to customize the look of the existing "self-service" module,
see XXXX instead.

=head1 PASSWORD, GECOS, SHELL CHANGING FUNCTIONS

=over 4

=item passwd

=item chfn

=item chsh

=back

=head1 "MY ACCOUNT" FUNCTIONS

=over 4

=item login HASHREF

Creates a user session.  Takes a hash reference as parameter with the
following keys:

=over 4

=item username

Username

=item domain

Domain

=item password

Password

=back

Returns a hash reference with the following keys:

=over 4

=item error

Empty on success, or an error message on errors.

=item session_id

Session identifier for successful logins

=back

=item customer_info HASHREF

Returns general customer information.

Takes a hash reference as parameter with a single key: B<session_id>

Returns a hash reference with the following keys:

=over 4

=item name

Customer name

=item balance

Balance owed

=item open

Array reference of hash references of open inoices.  Each hash reference has
the following keys: invnum, date, owed

=item small_custview

An HTML fragment containing shipping and billing addresses.

=item The following fields are also returned

first last company address1 address2 city county state zip country daytime night fax ship_first ship_last ship_company ship_address1 ship_address2 ship_city ship_state ship_zip ship_country ship_daytime ship_night ship_fax payby payinfo payname month year invoicing_list postal_invoicing

=back

=item edit_info HASHREF

Takes a hash reference as parameter with any of the following keys:

first last company address1 address2 city county state zip country daytime night fax ship_first ship_last ship_company ship_address1 ship_address2 ship_city ship_state ship_zip ship_country ship_daytime ship_night ship_fax payby payinfo paycvv payname month year invoicing_list postal_invoicing

If a field exists, the customer record is updated with the new value of that
field.  If a field does not exist, that field is not changed on the customer
record.

Returns a hash reference with a single key, B<error>, empty on success, or an
error message on errors

=item invoice HASHREF

Returns an invoice.  Takes a hash reference as parameter with two keys:
session_id and invnum

Returns a hash reference with the following keys:

=over 4

=item error

Empty on success, or an error message on errors

=item invnum

Invoice number

=item invoice_text

Invoice text

=back

=item list_invoices HASHREF

Returns a list of all customer invoices.  Takes a hash references with a single
key, session_id.

Returns a hash reference with the following keys:

=over 4

=item error

Empty on success, or an error message on errors

=item invoices

Reference to array of hash references with the following keys:

=over 4

=item invnum

Invoice ID

=item _date

Invoice date, in UNIX epoch time

=back

=back

=item cancel HASHREF

Cancels this customer.

Takes a hash reference as parameter with a single key: B<session_id>

Returns a hash reference with a single key, B<error>, which is empty on
success or an error message on errors.

=item payment_info HASHREF

Returns information that may be useful in displaying a payment page.

Takes a hash reference as parameter with a single key: B<session_id>.

Returns a hash reference with the following keys:

=over 4

=item error

Empty on success, or an error message on errors

=item balance

Balance owed

=item payname

Exact name on credit card (CARD/DCRD)

=item address1

Address line one

=item address2

Address line two

=item city

City

=item state

State

=item zip

Zip or postal code

=item payby

Customer's current default payment type.

=item card_type

For CARD/DCRD payment types, the card type (Visa card, MasterCard, Discover card, American Express card, etc.)

=item payinfo

For CARD/DCRD payment types, the card number

=item month

For CARD/DCRD payment types, expiration month

=item year

For CARD/DCRD payment types, expiration year

=item cust_main_county

County/state/country data - array reference of hash references, each of which has the fields of a cust_main_county record (see L<FS::cust_main_county>).  Note these are not FS::cust_main_county objects, but hash references of columns and values.

=item states

Array reference of all states in the current default country.

=item card_types

Hash reference of card types; keys are card types, values are the exact strings
passed to the process_payment function

=item paybatch

Unique transaction identifier (prevents multiple charges), passed to the
process_payment function

=back

=item process_payment HASHREF

Processes a payment and possible change of address or payment type.  Takes a
hash reference as parameter with the following keys:

=over 4

=item session_id

Session identifier

=item amount

Amount

=item save

If true, address and card information entered will be saved for subsequent
transactions.

=item auto

If true, future credit card payments will be done automatically (sets payby to
CARD).  If false, future credit card payments will be done on-demand (sets
payby to DCRD).  This option only has meaning if B<save> is set true.  

=item payname

Name on card

=item address1

Address line one

=item address2

Address line two

=item city

City

=item state

State

=item zip

Zip or postal code

=item payinfo

Card number

=item month

Card expiration month

=item year

Card expiration year

=item paybatch

Unique transaction identifier, returned from the payment_info function.
Prevents multiple charges.

=back

Returns a hash reference with a single key, B<error>, empty on success, or an
error message on errors

=item list_pkgs

Returns package information for this customer.  For more detail on services,
see L</list_svcs>.

Takes a hash reference as parameter with a single key: B<session_id>

Returns a hash reference containing customer package information.  The hash reference contains the following keys:

=over 4

=item custnum

Customer number

=item cust_pkg HASHREF

Array reference of hash references, each of which has the fields of a cust_pkg
record (see L<FS::cust_pkg>) as well as the fields below.  Note these are not
the internal FS:: objects, but hash references of columns and values.

=over 4

=item part_pkg fields

All fields of part_pkg for this specific cust_pkg (be careful with this
information - it may reveal more about your available packages than you would
like users to know in aggregate) 

=cut

#XXX pare part_pkg fields down to a more secure subset

=item part_svc

An array of hash references indicating information on unprovisioned services
available for provisioning for this specific cust_pkg.  Each has the following
keys:

=over 4

=item part_svc fields

All fields of part_svc (be careful with this information - it may reveal more
about your available packages than you would like users to know in aggregate) 

=cut

#XXX pare part_svc fields down to a more secure subset

=back

=item cust_svc

An array of hash references indicating information on the customer services
already provisioned for this specific cust_pkg.  Each has the following keys:

=over 4

=item label

Array reference with three elements:

=over 4

=item

Name of this service

=item

Meaningful user-specific identifier for the service (i.e. username, domain or mail alias)

=item

Table name of this service

=back

=item svcnum

Primary key for this service

=item svcpart

Service definition (part_pkg)

=item pkgnum

Customer package (cust_pkg)

=item overlimit

Blank if the service is not over limit, or the date the service exceeded its usage limit (as a UNIX timestamp).

=back

=back

=item error

Empty on success, or an error message on errors.

=back

=item list_svcs

Returns service information for this customer.

Takes a hash reference as parameter with a single key: B<session_id>

Returns a hash reference containing customer package information.  The hash reference contains the following keys:

=over 4

=item custnum

Customer number

=item svcs

An array of hash references indicating information on all of this customer's
services.  Each has the following keys:

=over 4

=item svcnum

Primary key for this service

=item label

Name of this service

=item value

Meaningful user-specific identifier for the service (i.e. username, domain, or
mail alias).

=back

Account (svc_acct) services also have the following keys:

=item username

Username

=item email

username@domain

=item seconds

Seconds remaining

=item upbytes

Upload bytes remaining

=item downbytes

Download bytes remaining

=item totalbytes

Total bytes remaining

=item recharge_amount

Cost of a recharge

=item recharge_seconds

Number of seconds gained by recharge

=item recharge_upbytes

Number of upload bytes gained by recharge

=item recharge_downbytes

Number of download bytes gained by recharge

=item recharge_totalbytes

Number of total bytes gained by recharge

=back

=back

=item order_pkg

Orders a package for this customer.

Takes a hash reference as parameter with the following keys:

=over 4

=item session_id

Session identifier

=item pkgpart

pkgpart of package to order

=item svcpart

optional svcpart, required only if the package definition does not contain
one svc_acct service definition with quantity 1 (it may contain others with
quantity >1)

=item username

Username

=item _password

Password

=item sec_phrase

Optional security phrase

=item popnum

Optional Access number number

=back

Returns a hash reference with a single key, B<error>, empty on success, or an
error message on errors.  The special error '_decline' is returned for
declined transactions.

=item cancel_pkg

Cancels a package for this customer.

Takes a hash reference as parameter with the following keys:

=over 4

=item session_id

Session identifier

=item pkgpart

pkgpart of package to cancel

=back

Returns a hash reference with a single key, B<error>, empty on success, or an
error message on errors.

=back

=head1 SIGNUP FUNCTIONS

=over 4

=item signup_info HASHREF

Takes a hash reference as parameter with the following keys:

=over 4

=item session_id - Optional agent/reseller interface session

=back

Returns a hash reference containing information that may be useful in
displaying a signup page.  The hash reference contains the following keys:

=over 4

=item cust_main_county

County/state/country data - array reference of hash references, each of which has the fields of a cust_main_county record (see L<FS::cust_main_county>).  Note these are not FS::cust_main_county objects, but hash references of columns and values.

=item part_pkg

Available packages - array reference of hash references, each of which has the fields of a part_pkg record (see L<FS::part_pkg>).  Each hash reference also has an additional 'payby' field containing an array reference of acceptable payment types specific to this package (see below and L<FS::part_pkg/payby>).  Note these are not FS::part_pkg objects, but hash references of columns and values.  Requires the 'signup_server-default_agentnum' configuration value to be set, or
an agentnum specified explicitly via reseller interface session_id in the
options.

=item agent

Array reference of hash references, each of which has the fields of an agent record (see L<FS::agent>).  Note these are not FS::agent objects, but hash references of columns and values.

=item agentnum2part_pkg

Hash reference; keys are agentnums, values are array references of available packages for that agent, in the same format as the part_pkg arrayref above.

=item svc_acct_pop

Access numbers - array reference of hash references, each of which has the fields of an svc_acct_pop record (see L<FS::svc_acct_pop>).  Note these are not FS::svc_acct_pop objects, but hash references of columns and values.

=item security_phrase

True if the "security_phrase" feature is enabled

=item payby

Array reference of acceptable payment types for signup

=over 4

=item CARD

credit card - automatic

=item DCRD

credit card - on-demand - version 1.5+ only

=item CHEK

electronic check - automatic

=item DCHK

electronic check - on-demand - version 1.5+ only

=item LECB

Phone bill billing

=item BILL

billing, not recommended for signups

=item COMP

free, definitely not recommended for signups

=item PREPAY

special billing type: applies a credit (see FS::prepay_credit) and sets billing type to BILL

=back

=item cvv_enabled

True if CVV features are available (1.5+ or 1.4.2 with CVV schema patch)

=item msgcat

Hash reference of message catalog values, to support error message customization.  Currently available keys are: passwords_dont_match, invalid_card, unknown_card_type, and not_a (as in "Not a Discover card").  Values are configured in the web interface under "View/Edit message catalog".

=item statedefault

Default state

=item countrydefault

Default country

=back

=item new_customer HASHREF

Creates a new customer.  Takes a hash reference as parameter with the
following keys:

=over 4

=item first

first name (required)

=item last

last name (required)

=item ss

(not typically collected; mostly used for ACH transactions)

=item company

Company name

=item address1 (required)

Address line one

=item address2

Address line two

=item city (required)

City

=item county

County

=item state (required)

State

=item zip (required)

Zip or postal code

=item daytime

Daytime phone number

=item night

Evening phone number

=item fax

Fax number

=item payby

CARD, DCRD, CHEK, DCHK, LECB, BILL, COMP or PREPAY (see L</signup_info> (required)

=item payinfo

Card number for CARD/DCRD, account_number@aba_number for CHEK/DCHK, prepaid "pin" for PREPAY, purchase order number for BILL

=item paycvv

Credit card CVV2 number (1.5+ or 1.4.2 with CVV schema patch)

=item paydate

Expiration date for CARD/DCRD

=item payname

Exact name on credit card for CARD/DCRD, bank name for CHEK/DCHK

=item invoicing_list

comma-separated list of email addresses for email invoices.  The special value 'POST' is used to designate postal invoicing (it may be specified alone or in addition to email addresses),

=item referral_custnum

referring customer number

=item agentnum

Agent number

=item pkgpart

pkgpart of initial package

=item username

Username

=item _password

Password

=item sec_phrase

Security phrase

=item popnum

Access number (index, not the literal number)

=item countrycode

Country code (to be provisioned as a service)

=item phonenum

Phone number (to be provisioned as a service)

=item pin

Voicemail PIN

=back

Returns a hash reference with the following keys:

=over 4

=item error

Empty on success, or an error message on errors.  The special error '_decline' is returned for declined transactions; other error messages should be suitable for display to the user (and are customizable in under Configuration | View/Edit message catalog)

=back

=item regionselector HASHREF | LIST

Takes as input a hashref or list of key/value pairs with the following keys:

=over 4

=item selected_county

Currently selected county

=item selected_state

Currently selected state

=item selected_country

Currently selected country

=item prefix

Specify a unique prefix string  if you intend to use the HTML output multiple time son one page.

=item onchange

Specify a javascript subroutine to call on changes

=item default_state

Default state

=item default_country

Default country

=item locales

An arrayref of hash references specifying regions.  Normally you can just pass the value of the I<cust_main_county> field returned by B<signup_info>.

=back

Returns a list consisting of three HTML fragments for county selection,
state selection and country selection, respectively.

=cut

#false laziness w/FS::cust_main_county (this is currently the "newest" version)
sub regionselector {
  my $param;
  if ( ref($_[0]) ) {
    $param = shift;
  } else {
    $param = { @_ };
  }
  $param->{'selected_country'} ||= $param->{'default_country'};
  $param->{'selected_state'} ||= $param->{'default_state'};

  my $prefix = exists($param->{'prefix'}) ? $param->{'prefix'} : '';

  my $countyflag = 0;

  my %cust_main_county;

#  unless ( @cust_main_county ) { #cache 
    #@cust_main_county = qsearch('cust_main_county', {} );
    #foreach my $c ( @cust_main_county ) {
    foreach my $c ( @{ $param->{'locales'} } ) {
      #$countyflag=1 if $c->county;
      $countyflag=1 if $c->{county};
      #push @{$cust_main_county{$c->country}{$c->state}}, $c->county;
      #$cust_main_county{$c->country}{$c->state}{$c->county} = 1;
      $cust_main_county{$c->{country}}{$c->{state}}{$c->{county}} = 1;
    }
#  }
  $countyflag=1 if $param->{selected_county};

  my $script_html = <<END;
    <SCRIPT>
    function opt(what,value,text) {
      var optionName = new Option(text, value, false, false);
      var length = what.length;
      what.options[length] = optionName;
    }
    function ${prefix}country_changed(what) {
      country = what.options[what.selectedIndex].text;
      for ( var i = what.form.${prefix}state.length; i >= 0; i-- )
          what.form.${prefix}state.options[i] = null;
END
      #what.form.${prefix}state.options[0] = new Option('', '', false, true);

  foreach my $country ( sort keys %cust_main_county ) {
    $script_html .= "\nif ( country == \"$country\" ) {\n";
    foreach my $state ( sort keys %{$cust_main_county{$country}} ) {
      my $text = $state || '(n/a)';
      $script_html .= qq!opt(what.form.${prefix}state, "$state", "$text");\n!;
    }
    $script_html .= "}\n";
  }

  $script_html .= <<END;
    }
    function ${prefix}state_changed(what) {
END

  if ( $countyflag ) {
    $script_html .= <<END;
      state = what.options[what.selectedIndex].text;
      country = what.form.${prefix}country.options[what.form.${prefix}country.selectedIndex].text;
      for ( var i = what.form.${prefix}county.length; i >= 0; i-- )
          what.form.${prefix}county.options[i] = null;
END

    foreach my $country ( sort keys %cust_main_county ) {
      $script_html .= "\nif ( country == \"$country\" ) {\n";
      foreach my $state ( sort keys %{$cust_main_county{$country}} ) {
        $script_html .= "\nif ( state == \"$state\" ) {\n";
          #foreach my $county ( sort @{$cust_main_county{$country}{$state}} ) {
          foreach my $county ( sort keys %{$cust_main_county{$country}{$state}} ) {
            my $text = $county || '(n/a)';
            $script_html .=
              qq!opt(what.form.${prefix}county, "$county", "$text");\n!;
          }
        $script_html .= "}\n";
      }
      $script_html .= "}\n";
    }
  }

  $script_html .= <<END;
    }
    </SCRIPT>
END

  my $county_html = $script_html;
  if ( $countyflag ) {
    $county_html .= qq!<SELECT NAME="${prefix}county" onChange="$param->{'onchange'}">!;
    $county_html .= '</SELECT>';
  } else {
    $county_html .=
      qq!<INPUT TYPE="hidden" NAME="${prefix}county" VALUE="$param->{'selected_county'}">!;
  }

  my $state_html = qq!<SELECT NAME="${prefix}state" !.
                   qq!onChange="${prefix}state_changed(this); $param->{'onchange'}">!;
  foreach my $state ( sort keys %{ $cust_main_county{$param->{'selected_country'}} } ) {
    my $text = $state || '(n/a)';
    my $selected = $state eq $param->{'selected_state'} ? 'SELECTED' : '';
    $state_html .= "\n<OPTION $selected VALUE=$state>$text</OPTION>"
  }
  $state_html .= '</SELECT>';

  $state_html .= '</SELECT>';

  my $country_html = qq!<SELECT NAME="${prefix}country" !.
                     qq!onChange="${prefix}country_changed(this); $param->{'onchange'}">!;
  my $countrydefault = $param->{default_country} || 'US';
  foreach my $country (
    sort { ($b eq $countrydefault) <=> ($a eq $countrydefault) or $a cmp $b }
      keys %cust_main_county
  ) {
    my $selected = $country eq $param->{'selected_country'} ? ' SELECTED' : '';
    $country_html .= "\n<OPTION$selected>$country</OPTION>"
  }
  $country_html .= '</SELECT>';

  ($county_html, $state_html, $country_html);

}

#=item expselect HASHREF | LIST
#
#Takes as input a hashref or list of key/value pairs with the following keys:
#
#=over 4
#
#=item prefix - Specify a unique prefix string  if you intend to use the HTML output multiple time son one page.
#
#=item date - current date, in yyyy-mm-dd or m-d-yyyy format
#
#=back

=item expselect PREFIX [ DATE ]

Takes as input a unique prefix string and the current expiration date, in
yyyy-mm-dd or m-d-yyyy format

Returns an HTML fragments for expiration date selection.

=cut

sub expselect {
  #my $param;
  #if ( ref($_[0]) ) {
  #  $param = shift;
  #} else {
  #  $param = { @_ };
  #my $prefix = $param->{'prefix'};
  #my $prefix = exists($param->{'prefix'}) ? $param->{'prefix'} : '';
  #my $date =   exists($param->{'date'})   ? $param->{'date'}   : '';
  my $prefix = shift;
  my $date = scalar(@_) ? shift : '';

  my( $m, $y ) = ( 0, 0 );
  if ( $date  =~ /^(\d{4})-(\d{2})-\d{2}$/ ) { #PostgreSQL date format
    ( $m, $y ) = ( $2, $1 );
  } elsif ( $date =~ /^(\d{1,2})-(\d{1,2}-)?(\d{4}$)/ ) {
    ( $m, $y ) = ( $1, $3 );
  }
  my $return = qq!<SELECT NAME="$prefix!. qq!_month" SIZE="1">!;
  for ( 1 .. 12 ) {
    $return .= qq!<OPTION VALUE="$_"!;
    $return .= " SELECTED" if $_ == $m;
    $return .= ">$_";
  }
  $return .= qq!</SELECT>/<SELECT NAME="$prefix!. qq!_year" SIZE="1">!;
  my @t = localtime;
  my $thisYear = $t[5] + 1900;
  for ( ($thisYear > $y && $y > 0 ? $y : $thisYear) .. ($thisYear+10) ) {
    $return .= qq!<OPTION VALUE="$_"!;
    $return .= " SELECTED" if $_ == $y;
    $return .= ">$_";
  }
  $return .= "</SELECT>";

  $return;
}

=item popselector HASHREF | LIST

Takes as input a hashref or list of key/value pairs with the following keys:

=over 4

=item popnum

Access number number

=item pops

An arrayref of hash references specifying access numbers.  Normally you can just pass the value of the I<svc_acct_pop> field returned by B<signup_info>.

=back

Returns an HTML fragment for access number selection.

=cut

#horrible false laziness with FS/FS/svc_acct_pop.pm::popselector
sub popselector {
  my $param;
  if ( ref($_[0]) ) {
    $param = shift;
  } else {
    $param = { @_ };
  }
  my $popnum = $param->{'popnum'};
  my $pops = $param->{'pops'};

  return '<INPUT TYPE="hidden" NAME="popnum" VALUE="">' unless @$pops;
  return $pops->[0]{city}. ', '. $pops->[0]{state}.
         ' ('. $pops->[0]{ac}. ')/'. $pops->[0]{exch}. '-'. $pops->[0]{loc}.
         '<INPUT TYPE="hidden" NAME="popnum" VALUE="'. $pops->[0]{popnum}. '">'
    if scalar(@$pops) == 1;

  my %pop = ();
  my %popnum2pop = ();
  foreach (@$pops) {
    push @{ $pop{ $_->{state} }->{ $_->{ac} } }, $_;
    $popnum2pop{$_->{popnum}} = $_;
  }

  my $text = <<END;
    <SCRIPT>
    function opt(what,href,text) {
      var optionName = new Option(text, href, false, false)
      var length = what.length;
      what.options[length] = optionName;
    }
END

  my $init_popstate = $param->{'init_popstate'};
  if ( $init_popstate ) {
    $text .= '<INPUT TYPE="hidden" NAME="init_popstate" VALUE="'.
             $init_popstate. '">';
  } else {
    $text .= <<END;
      function acstate_changed(what) {
        state = what.options[what.selectedIndex].text;
        what.form.popac.options.length = 0
        what.form.popac.options[0] = new Option("Area code", "-1", false, true);
END
  } 

  my @states = $init_popstate ? ( $init_popstate ) : keys %pop;
  foreach my $state ( sort { $a cmp $b } @states ) {
    $text .= "\nif ( state == \"$state\" ) {\n" unless $init_popstate;

    foreach my $ac ( sort { $a cmp $b } keys %{ $pop{$state} }) {
      $text .= "opt(what.form.popac, \"$ac\", \"$ac\");\n";
      if ($ac eq $param->{'popac'}) {
        $text .= "what.form.popac.options[what.form.popac.length-1].selected = true;\n";
      }
    }
    $text .= "}\n" unless $init_popstate;
  }
  $text .= "popac_changed(what.form.popac)}\n";

  $text .= <<END;
  function popac_changed(what) {
    ac = what.options[what.selectedIndex].text;
    what.form.popnum.options.length = 0;
    what.form.popnum.options[0] = new Option("City", "-1", false, true);

END

  foreach my $state ( @states ) {
    foreach my $popac ( keys %{ $pop{$state} } ) {
      $text .= "\nif ( ac == \"$popac\" ) {\n";

      foreach my $pop ( @{$pop{$state}->{$popac}}) {
        my $o_popnum = $pop->{popnum};
        my $poptext =  $pop->{city}. ', '. $pop->{state}.
                       ' ('. $pop->{ac}. ')/'. $pop->{exch}. '-'. $pop->{loc};

        $text .= "opt(what.form.popnum, \"$o_popnum\", \"$poptext\");\n";
        if ($popnum == $o_popnum) {
          $text .= "what.form.popnum.options[what.form.popnum.length-1].selected = true;\n";
        }
      }
      $text .= "}\n";
    }
  }


  $text .= "}\n</SCRIPT>\n";

  $text .=
    qq!<TABLE CELLPADDING="0"><TR><TD><SELECT NAME="acstate"! .
    qq!SIZE=1 onChange="acstate_changed(this)"><OPTION VALUE=-1>State!;
  $text .= "<OPTION" . ($_ eq $param->{'acstate'} ? " SELECTED" : "") .
           ">$_" foreach sort { $a cmp $b } @states;
  $text .= '</SELECT>'; #callback? return 3 html pieces?  #'</TD>';

  $text .=
    qq!<SELECT NAME="popac" SIZE=1 onChange="popac_changed(this)">!.
    qq!<OPTION>Area code</SELECT></TR><TR VALIGN="top">!;

  $text .= qq!<TR><TD><SELECT NAME="popnum" SIZE=1 STYLE="width: 20em"><OPTION>City!;


  #comment this block to disable initial list polulation
  my @initial_select = ();
  if ( scalar( @$pops ) > 100 ) {
    push @initial_select, $popnum2pop{$popnum} if $popnum2pop{$popnum};
  } else {
    @initial_select = @$pops;
  }
  foreach my $pop ( sort { $a->{state} cmp $b->{state} } @initial_select ) {
    $text .= qq!<OPTION VALUE="!. $pop->{popnum}. '"'.
             ( ( $popnum && $pop->{popnum} == $popnum ) ? ' SELECTED' : '' ). ">".
             $pop->{city}. ', '. $pop->{state}.
               ' ('. $pop->{ac}. ')/'. $pop->{exch}. '-'. $pop->{loc};
  }

  $text .= qq!</SELECT></TD></TR></TABLE>!;

  $text;

}

=item domainselector HASHREF | LIST

Takes as input a hashref or list of key/value pairs with the following keys:

=over 4

=item pkgnum

Package number

=item domsvc

Service number of the selected item.

=back

Returns an HTML fragment for domain selection.

=cut

sub domainselector {
  my $param;
  if ( ref($_[0]) ) {
    $param = shift;
  } else {
    $param = { @_ };
  }
  my $domsvc= $param->{'domsvc'};
  my $rv = 
      domain_select_hash(map {$_ => $param->{$_}} qw(pkgnum svcpart pkgpart) );
  my $domains = $rv->{'domains'};
  $domsvc = $rv->{'domsvc'} unless $domsvc;

  return '<INPUT TYPE="hidden" NAME="domsvc" VALUE="">'
    unless scalar(keys %$domains);

  if (scalar(keys %$domains) == 1) {
    my $key;
    foreach(keys %$domains) {
      $key = $_;
    }
    return '<TR><TD ALIGN="right">Domain</TD><TD>'. $domains->{$key}.
           '<INPUT TYPE="hidden" NAME="domsvc" VALUE="'. $key. '"></TD></TR>'
  }

  my $text .= qq!<TR><TD ALIGN="right">Domain</TD><TD><SELECT NAME="domsvc" SIZE=1 STYLE="width: 20em"><OPTION>(Choose Domain)!;


  foreach my $domain ( sort { $domains->{$a} cmp $domains->{$b} } keys %$domains ) {
    $text .= qq!<OPTION VALUE="!. $domain. '"'.
             ( ( $domsvc && $domain == $domsvc ) ? ' SELECTED' : '' ). ">".
             $domains->{$domain};
  }

  $text .= qq!</SELECT></TD></TR>!;

  $text;

}

=item didselector HASHREF | LIST

Takes as input a hashref or list of key/value pairs with the following keys:

=over 4

=item field

=item svcpart

=back

Returns an HTML fragment for DID selection.

=cut

sub didselector {
  my $param;
  if ( ref($_[0]) ) {
    $param = shift;
  } else {
    $param = { @_ };
  }

  my $rv = mason_comp( 'comp'=>'/elements/select-did.html',
                       'args'=>[ %$param ],
                     );

  #hmm.
  $rv->{'error'} || $rv->{'output'};

}

=back

=head1 RESELLER FUNCTIONS

Note: Resellers can also use the B<signup_info> and B<new_customer> functions
with their active session, and the B<customer_info> and B<order_pkg> functions
with their active session and an additional I<custnum> parameter.

=over 4

=item agent_login

=item agent_info

=item agent_list_customers

=back

=head1 BUGS

=head1 SEE ALSO

L<freeside-selfservice-clientd>, L<freeside-selfservice-server>

=cut

1;

