# -*- Mode: perl; tab-width: 4; indent-tabs-mode: nil; -*-
################################
# Quiz Module                  #
################################
# some of these ideas are stolen from moxquizz (an eggdrop module)
# see http://www.meta-x.de/moxquizz/

package BotModules::Quiz;
use vars qw(@ISA);
@ISA = qw(BotModules);
1;

# XXX high score table
# XXX do something with level
# XXX make bot able to self-abort if no-one is taking part
# XXX implement feature so that users that can be quiz admins in certain channels
# XXX accept user submission
# XXX README for database format (for now see http://www.meta-x.de/moxquizz/README.database)
# XXX pause doesn't stop count of how long answer takes to answer
# XXX different quiz formats, e.g. university challenge, weakest link (maybe implement by inheritance?)
# XXX stats, e.g. number of questions skipped
# XXX category filtering

sub Help {
    my $self = shift;
    my($event) = @_;
    my $help = {
        '' => "Runs quizzes. Start a quiz with the $self->{'prefix'}ask command.",
        $self->{'prefix'}.'ask' => 'Starts a quiz.',
        $self->{'prefix'}.'pause' => "Pauses the current quiz. Resume with $self->{'prefix'}resume.",
        $self->{'prefix'}.'resume' => 'Resumes the current quiz.',
        $self->{'prefix'}.'repeat' => 'Repeats the current question.',
        $self->{'prefix'}.'endquiz' => 'Ends the current quiz.',
        $self->{'prefix'}.'next' => 'Jump to the next question (at least half of the active participants have to say this for the question to be skipped).',
        $self->{'prefix'}.'score' => 'Show the current scores for the round.',
    };
    if ($self->isAdmin($event)) {
        $help->{'reload'} = 'To just reload the quiz data files instead of the whole module, use: reload Quiz Data';
    }
    return $help;
}

# RegisterConfig - Called when initialised, should call registerVariables
sub RegisterConfig {
    my $self = shift;
    $self->SUPER::RegisterConfig(@_);
    $self->registerVariables(
      # [ name, save?, settable? ]
        ['questionSets', 1, 1, ['trivia.en']], # the list of files to read (from the Quiz/ directory)
        ['questions', 0, 0, []], # the list of questions (hashes)
        ['categories', 0, 0, {}], # hash of arrays whose values are indexes into questions
        ['questionsPerRound', 1, 1, -1], # how many questions per round (-1 = infinite)
        ['currentQuestion', 1, 0, {}], # the active question (per-channel hash)
        ['questionIndex', 1, 0, 0], # where to start when picking the next question
        ['skipMargin', 1, 1, 10], # maximum number of questions to skip at a time
        ['remainingQuestions', 1, 0, {}], # how many more questions this round (per-channel hash)
        ['questionsTime', 1, 0, {}], # when the question was asked
        ['quizTime', 1, 0, {}], # when the quiz was started
        ['paused', 1, 0, {}], # if the game is paused
        ['totalScores', 1, 1, {}], # user => score
        ['quizScores', 1, 0, {}], # channel => "user score"
        ['skip', 1, 0, {}], # channel => "user 1"
        ['players', 1, 0, {}], # channel => "user last time"
        ['tip', 1, 0, {}], # which tip should next be given on this channel
        ['tipDelay', 1, 1, 10], # seconds to wait before giving a tip
        ['timeout', 1, 1, 120], # seconds to wait before giving up
        ['skipFractionRequired', 1, 1, 0.5], # fraction of players that must say !skip to skip
        ['askDelay', 1, 1, 2], # how long to wait between answer and question
        ['prefix', 1, 1, '!'], # the prefix to have at the start of commands
    );
}

sub Schedule {
    my $self = shift;
    my($event) = @_;
    $self->reloadData($event);
    my $fakeEvent = {%$event};
    foreach my $channel (keys %{$self->{'currentQuestion'}}) {
        $fakeEvent->{'channel'} = $channel;
        $fakeEvent->{'target'} = $channel;
        $self->debug("Restarting quiz in $channel... (qid $self->{'questionsTime'}->{$channel})");
        $self->schedule($fakeEvent, \$self->{'tipDelay'}, 1, 'tip', $self->{'questionsTime'}->{$channel});
        $self->schedule($fakeEvent, \$self->{'timeout'}, 1, 'timeout', $self->{'questionsTime'}->{$channel});
        if ($self->{'questionsTime'}->{$event->{'channel'}} == 0) {
            $self->schedule($event, \$self->{'askDelay'}, 1, 'ask');
        }
    }
    $self->SUPER::Schedule($event);
}

sub Told {
    my $self = shift;
    my($event, $message) = @_;
    if ($message =~ /^\s*reload\s+quiz\s+data\s*$/osi and $self->isAdmin($event)) {
        my $count = $self->reloadData($event);
        $self->say($event, "$count questions loaded");
    } elsif ($message =~ /^\s*status[?\s]*$/osi) {
        my $questions = @{$self->{'questions'}};
        my $quizzes = keys %{$self->{'currentQuestion'}};
        $self->say($event, "$event->{'from'}: I have $questions questions and am running $quizzes quizzes.", 1); # XXX 1 quizzes
    } elsif (not $self->DoQuizCheck($event, $message, 1)) {
        return $self->SUPER::Told(@_);
    }
    return 0; # we've dealt with it, no need to do anything else.
}

sub Baffled {
    my $self = shift;
    my($event, $message) = @_;
    if (not $self->quizAnswer($event, $message)) {
        return $self->SUPER::Baffled(@_);
    }
    return 0; # we've dealt with it, no need to do anything else.
}

sub Heard {
    my $self = shift;
    my($event, $message) = @_;
    if (not $self->DoQuizCheck($event, $message, 0) and
        not $self->quizAnswer($event, $message)) {
        return $self->SUPER::Heard(@_);
    }
    return 0; # we've dealt with it, no need to do anything else.
}

sub DoQuizCheck {
    my $self = shift;
    my($event, $message, $direct) = @_;
    if ($message =~ /^\s*\Q$self->{'prefix'}\Eask\s*$/si) {
        $self->quizStart($event);
    } elsif ($message =~ /^\s*\Q$self->{'prefix'}\Epause\s*$/si) {
        $self->quizPause($event);
    } elsif ($message =~ /^\s*\Q$self->{'prefix'}\E(?:resume|unpause)\s*$/si) {
        $self->quizResume($event);
    } elsif ($message =~ /^\s*\Q$self->{'prefix'}\Erepeat\s*$/si) {
        $self->quizRepeat($event);
    } elsif ($message =~ /^\s*\Q$self->{'prefix'}\E(?:end|stop|strivia|exit)(?:quiz)?\s*$/si) {
        $self->quizEnd($event);
    } elsif ($message =~ /^\s*\Q$self->{'prefix'}\E(?:dunno|skip|next)\s*$/si) {
        $self->quizSkip($event);
    } elsif ($message =~ /^\s*\Q$self->{'prefix'}\E(?:scores)\s*$/si) {
        $self->quizScores($event);
    } else {
        return 0;
    }
    return 1;
}

sub reloadData {
    my $self = shift;
    my($event) = @_;
    $self->{'questions'} = [];
    $self->{'categories'} = {};
    $self->debug('Loading quiz data...');
    foreach my $set (@{$self->{'questionSets'}}) {
        if ($set =~ m/^[a-zA-Z0-9-][a-zA-Z0-9.-]*$/os) {
            local *FILE;
            if (not open(FILE, "<BotModules/Quiz/$set")) { # XXX what if the directory has changed?
                $self->debug("  * $set (Not loaded; $!)");
                next;
            }
            $self->debug("  * $set");
            my $category;
            my $question = {'tip' => []};
            while (defined($_ = <FILE>)) {
                chomp;
                next if m/^\#/os; # skip comment lines
                next if m/^\s*$/os; # skip blank lines
                if (m/^Category:\s*(.*?)\s*$/os) {
                    #  Category?                              (should always be on top!)
                    $category = $1;
                    if (not defined($self->{'categories'}->{$category})) {
                        $self->{'categories'}->{$category} = [];
                    }
                } elsif (m/^Question:\s*(.*?)\s*$/os) {
                    #  Question                               (should always stand after Category)
                    $question = {'question' => $1, 'tip' => []};
                    if (defined($category)) {
                        $question->{'category'} = $category;
                        undef($category);
                    }
                    push(@{$self->{'questions'}}, $question);
                    push(@{$self->{'categories'}->{$category}}, $#{$self->{'questions'}});
                } elsif (m/^Answer:\s*(?:(.*?)\#(.*?)\#(.*?)|(.*?))\s*$/os) {
                    #  Answer                                 (will be matched if no regexp is provided)
                    if (defined($1)) {
                        $question->{'answer-long'} = "$1$2$3";
                        $question->{'answer-short'} = $2;
                    } else {
                        $question->{'answer-long'} = $4;
                        $question->{'answer-short'} = $4;
                    }
                } elsif (m/^Regexp:\s*(.*?)\s*$/os) {
                    #  Regexp?                                (use UNIX-style expressions)
                    $question->{'answer-regexp'} = $1;
                } elsif (m/^Author:\s*(.*?)\s*$/os) {
                    #  Author?                                (the brain behind this question)
                    $question->{'author'} = $1;
                } elsif (m/^Level:\s*(.*?)\s*$/os) {
                    #  Level? [baby|easy|normal|hard|extreme] (difficulty)
                    $question->{'level'} = $1;
                } elsif (m/^Comment:\s*(.*?)\s*$/os) {
                    #  Comment?                               (comment line)
                    $question->{'comment'} = $1;
                } elsif (m/^Score:\s*(.*?)\s*$/os) {
                    #  Score? [#]                             (credits for answering this question)
                    $question->{'score'} = $1;
                } elsif (m/^Tip:\s*(.*?)\s*$/os) {
                    #  Tip*                                   (provide one or more hints)
                    push(@{$question->{'tip'}}, $1);
                } elsif (m/^TipCycle:\s*(.*?)\s*$/os) {
                    #  TipCycle? [#]                          (Specify number of generated tips)
                    $question->{'tip-cycle'} = $1;
                } else {
                    # XXX error handling
                }
            }
            close(FILE);
        } # else XXX invalid filename, ignore it
    }
    # if no more questions, abort running quizes.
    if (not @{$self->{'questions'}}) {
        foreach my $channel (keys %{$self->{'currentQuestion'}}) {
            local $event->{'channel'} = $channel;
            $self->say($event, 'There are no more questions.');
            $self->quizEnd($event);
        }
    }
    return scalar(@{$self->{'questions'}});
}


# game implementation

sub Scheduled {
    my $self = shift;
    my($event, @data) = @_;
    if ($data[0] eq 'tip') {
        if ($self->{'questionsTime'}->{$event->{'channel'}} == $data[1] and
            defined($self->{'currentQuestion'}->{$event->{'channel'}})) {
            # $self->debug('time for a tip');
            if ($self->{'paused'}->{$event->{'channel'}} or
                $self->quizTip($event)) {
                $self->schedule($event, \$self->{'tipDelay'}, 1, @data);
            }
        }
    } elsif ($data[0] eq 'timeout') {
        if ($self->{'questionsTime'}->{$event->{'channel'}} == $data[1] and
            defined($self->{'currentQuestion'}->{$event->{'channel'}})) {
            if ($self->{'paused'}->{$event->{'channel'}}) {
                $self->schedule($event, \$self->{'timeout'}, 1, @data);
            } else {
                my $answer = $self->{'questions'}->[$self->{'currentQuestion'}->{$event->{'channel'}}]->{'answer-long'};
                $self->say($event, "Too late! The answer was: $answer");
                $self->quizQuestion($event);
            }
        }
    } elsif ($data[0] eq 'ask') {
        if (defined($self->{'currentQuestion'}->{$event->{'channel'}})) {
            $self->quizQuestion($event);
        }
    } else {
        $self->SUPER::Scheduled($event, @data);
    }
}

sub quizStart { # called by user
    my $self = shift;
    my($event) = @_;
    if ($event->{'channel'} ne '' and
        not defined($self->{'currentQuestion'}->{$event->{'channel'}})) {
        if (@{$self->{'questions'}} == 0) {
            # if no questions, complain.
            $self->say($event, 'I cannot run a quiz with no questions!');
        } else {
            # no game in progress, start one
            $self->{'remainingQuestions'}->{$event->{'channel'}} = $self->{'questionsPerRound'};
            $self->{'paused'}->{$event->{'channel'}} = 0;
            $self->{'quizTime'}->{$event->{'channel'}} = $event->{'time'};
            $self->{'quizScores'}->{$event->{'channel'}} = '';
            $self->{'players'}->{$event->{'channel'}} = '';
            $self->quizQuestion($event);
        }
    }
}

sub quizQuestion { # called from quizStart or delayed from quizAnswer
    my $self = shift;
    my($event) = @_;
    if ($event->{'channel'} ne '' and # in channel
        not $self->{'paused'}->{$event->{'channel'}}) { # quiz not paused
        if ($self->{'remainingQuestions'}->{$event->{'channel'}} != 0) {
            $self->{'remainingQuestions'}->{$event->{'channel'}}--;
            my $category = $self->{'questions'}->[$self->{'currentQuestion'}->{$event->{'channel'}}]->{'category'};
            my $try = 0;
            my $questionCount = scalar keys %{$self->{'questions'}};
            while ($self->{'questions'}->[$self->{'currentQuestion'}->{$event->{'channel'}}]->{'category'} eq $category
                   and $try++ < $questionCount) {
                $self->{'currentQuestion'}->{$event->{'channel'}} = $self->pickQuestion($event);
            }
            $self->{'questionsTime'}->{$event->{'channel'}} = $event->{'time'};
            $self->{'tip'}->{$event->{'channel'}} = 0;
            $self->{'skip'}->{$event->{'channel'}} = '';
            $self->schedule($event, \$self->{'tipDelay'}, 1, 'tip', $self->{'questionsTime'}->{$event->{'channel'}});
            $self->schedule($event, \$self->{'timeout'}, 1, 'timeout', $self->{'questionsTime'}->{$event->{'channel'}});
            $self->say($event, "Question: $self->{'questions'}->[$self->{'currentQuestion'}->{$event->{'channel'}}]->{'question'}");
            $self->debug("Question: $self->{'questions'}->[$self->{'currentQuestion'}->{$event->{'channel'}}]->{'question'}");
            $self->debug("Answer: $self->{'questions'}->[$self->{'currentQuestion'}->{$event->{'channel'}}]->{'answer-long'}");
            $self->saveConfig();
        } else {
            $self->quizEnd($event);
        }
    }
}

sub quizAnswer { # called by user
    my $self = shift;
    my($event, $message) = @_;
    if ($event->{'channel'} ne '' and # in channel
        defined($self->{'currentQuestion'}->{$event->{'channel'}}) and # in quiz
        $self->{'questionsTime'}->{$event->{'channel'}} and # not answered
        not $self->{'paused'}->{$event->{'channel'}}) { # quiz not paused
        $self->stringHash(\$self->{'players'}->{$event->{'channel'}}, $event->{'from'}, $event->{'time'});
        if (lc($message) eq lc($self->{'questions'}->[$self->{'currentQuestion'}->{$event->{'channel'}}]->{'answer-long'}) or
            (defined($self->{'questions'}->[$self->{'currentQuestion'}->{$event->{'channel'}}]->{'answer-short'}) and
             lc($message) eq lc($self->{'questions'}->[$self->{'currentQuestion'}->{$event->{'channel'}}]->{'answer-short'})) or
            (defined($self->{'questions'}->[$self->{'currentQuestion'}->{$event->{'channel'}}]->{'answer-regexp'}) and
             $message =~ /$self->{'questions'}->[$self->{'currentQuestion'}->{$event->{'channel'}}]->{'answer-regexp'}/si)) {
            # they got it right
            my $who = $event->{'from'};
            my $answer = $self->{'questions'}->[$self->{'currentQuestion'}->{$event->{'channel'}}]->{'answer-long'};
            my $score = $self->{'questions'}->[$self->{'currentQuestion'}->{$event->{'channel'}}]->{'score'};
            if (not defined($score)) {
                $score = 1; # use difficulty XXX
            }
            my $time = $event->{'time'} - $self->{'questionsTime'}->{$event->{'channel'}};
            my $total = $self->score($event, $who, $score);
            $self->debug("Answered by: $who");
            $self->say($event, "$who got the right answer in $time seconds (+$score points giving $total). The answer was: $answer");
            $self->saveConfig();
            $self->{'questionsTime'}->{$event->{'channel'}} = 0;
            $self->schedule($event, \$self->{'askDelay'}, 1, 'ask');
        }
    }
}

sub quizTip { # called by timer, only during game
    my $self = shift;
    my($event) = @_;
    my $tip;
    if (defined($self->{'questions'}->[$self->{'currentQuestion'}->{$event->{'channel'}}]->{'tips'}) and
        $self->{'tip'}->{$event->{'channel'}} < @{$self->{'questions'}->[$self->{'currentQuestion'}->{$event->{'channel'}}]->{'tips'}}) {
        $tip = $self->{'questions'}->[$self->{'currentQuestion'}->{$event->{'channel'}}]->{'tips'}->[$self->{'tip'}->{$event->{'channel'}}];
    } else {
        if (not defined($self->{'questions'}->[$self->{'currentQuestion'}->{$event->{'channel'}}]->{'tips'}) and
            (not defined($self->{'questions'}->[$self->{'currentQuestion'}->{$event->{'channel'}}]->{'tipCycle'}) or
             $self->{'tip'}->{$event->{'channel'}} < $self->{'questions'}->[$self->{'currentQuestion'}->{$event->{'channel'}}]->{'tipCycle'})) {
            $tip = $self->generateTip($self->{'questions'}->[$self->{'currentQuestion'}->{$event->{'channel'}}]->{'answer-long'},
                                      $self->{'tip'}->{$event->{'channel'}},
                                      $self->{'questions'}->[$self->{'currentQuestion'}->{$event->{'channel'}}]->{'tipCycle'});
        }
    }
    if (defined($tip)) {
        $self->{'tip'}->{$event->{'channel'}} += 1;
        $self->say($event, "Hint: $tip...");
        $self->saveConfig();
        return 1;
    } else {
        return 0;
    }
}

sub quizPause { # called by user
    my $self = shift;
    my($event) = @_;
    if (defined($self->{'currentQuestion'}->{$event->{'channel'}})) { # game in progress
        if (not $self->{'paused'}->{$event->{'channel'}}) { # not paused
            # pause game
            $self->{'paused'}->{$event->{'channel'}} = 1;
            $self->saveConfig();
            $self->say($event, "Quiz paused. Use $self->{'prefix'}resume to continue.");
        } else {
            $self->say($event, "Quiz already paused. Use $self->{'prefix'}resume to continue.");
        }
    } else {
        $self->say($event, "No quiz in progress, use $self->{'prefix'}ask to start one.");
    }
}

sub quizResume { # called by user
    my $self = shift;
    my($event) = @_;
    if (defined($self->{'currentQuestion'}->{$event->{'channel'}})) { # game in progress
        if ($self->{'paused'}->{$event->{'channel'}}) { # paused
            # unpause game
            $self->{'paused'}->{$event->{'channel'}} = 0;
            $self->saveConfig();
            $self->say($event, "Quiz resumed. Question: $self->{'questions'}->[$self->{'currentQuestion'}->{$event->{'channel'}}]->{'question'}");
        } else {
            $self->say($event, "Quiz already in progress. Use $self->{'prefix'}repeat to be told the question again, and $self->{'prefix'}pause to pause the quiz.");
        }
    } else {
        $self->say($event, "No quiz in progress, use $self->{'prefix'}ask to start one.");
    }
}

sub quizRepeat { # called by user
    my $self = shift;
    my($event) = @_;
    if (defined($self->{'currentQuestion'}->{$event->{'channel'}})) { # game in progress
        $self->say($event, "Question: $self->{'questions'}->[$self->{'currentQuestion'}->{$event->{'channel'}}]->{'question'}");
    } else {
        $self->say($event, "No quiz in progress, use $self->{'prefix'}ask to start one.");
    }
}

sub quizEnd { # called by question and user
    my $self = shift;
    my($event) = @_;
    if (defined($self->{'currentQuestion'}->{$event->{'channel'}})) {
        # get the scores for each player that player in the game
        my @scores = $self->getScores($event, sub {
                                          my($event, $score) = @_;
                                          # XXX this means that a user has to be there till the end
                                          # of the game to get points added to his high score table.
                                          # XXX it also means a user can get better simply by
                                          # playing more games.
                                          $self->{'totalScores'}->{$score->[1]} += $score->[2];
                                      });
        # print them
        if (@scores) {
            local $" = ', ';
            $self->say($event, "Quiz Ended. Scores: @scores");
        } else {
            $self->say($event, 'Quiz Ended. No questions were answered.');
        }
        delete($self->{'currentQuestion'}->{$event->{'channel'}});
        $self->saveConfig();
    }
}

sub quizScores { # called by user
    my $self = shift;
    my($event) = @_;
    if (defined($self->{'currentQuestion'}->{$event->{'channel'}})) {
        # get the scores for each player that player in the game
        my @scores = $self->getScores($event, sub {});
        # get other stats
        my $remaining = '';
        if ($self->{'remainingQuestions'}->{$event->{'channel'}} > 0) {
            $remaining = " There are $self->{'remainingQuestions'}->{$event->{'channel'}} more questions to go.";
        }
        # print them
        if (@scores) {
            local $" = ', ';
            $self->say($event, "Current Scores: @scores$remaining");
        } else {
            $self->say($event, "No questions have been answered yet.$remaining");
        }
    } else {
        $self->say($event, "No quiz in progress, use $self->{'prefix'}ask to start one.");
    }
}

sub quizSkip { # called by user
    my $self = shift;
    my($event) = @_;
    if (defined($self->{'currentQuestion'}->{$event->{'channel'}})) { # game in progress
        if (not $self->{'paused'}->{$event->{'channel'}}) { # not paused
            if ($self->{'questionsTime'}->{$event->{'channel'}}) { # question asked and not answered
                # XXX should only let players skip (at the moment even someone who has not tried to answer any question can skip)
                # Get number of users who have said !skip (and set current user)
                my(undef, $skipCount) = $self->stringHash(\$self->{'skip'}->{$event->{'channel'}}, $event->{'from'}, 1);
                # Get number of users who are playing
                my $playerCount = $self->getActivePlayers($event);
                if ($skipCount >= $playerCount * $self->{'skipFractionRequired'}) {
                    my $answer = $self->{'questions'}->[$self->{'currentQuestion'}->{$event->{'channel'}}]->{'answer-long'};
                    $self->say($event, "$skipCount players wanted to skip. Moving to next question. The answer was: $answer");
                    $self->quizQuestion($event);
                }
            } # else drop it
        } else {
            $self->say($event, "Quiz paused. Use $self->{'prefix'}resume to continue the quiz.");
        }
    } else {
        $self->say($event, "No quiz in progress, use $self->{'prefix'}ask to start one.");
    }
}

sub pickQuestion {
    my $self = shift;
    my($event) = @_;
    $self->{'questionIndex'} += 1 + $event->{'time'} % $self->{'skipMargin'};
    $self->{'questionIndex'} %= @{$self->{'questions'}};
    return $self->{'questionIndex'};
}

sub score {
    my $self = shift;
    my($event, $who, $score) = @_;
    if (defined($self->{'currentQuestion'}->{$event->{'channel'}})) {
        my($score, undef) = $self->stringHash(\$self->{'quizScores'}->{$event->{'channel'}}, $who, $score, 1);
        $self->saveConfig();
        return $score;
    }
}

sub getScores {
    my $self = shift;
    my($event, $perUser) = @_;
    my @scores;
    foreach my $player ($self->getActivePlayers($event)) {
        my($score, undef) = $self->stringHash(\$self->{'quizScores'}->{$event->{'channel'}}, $player);
        if (defined($score)) {
            push(@scores, ["$player: $score", $player, $score]);
        }
    }
    # sort the scores by number
    @scores = sort {$a->[2] <=> $b->[2]} @scores;
    foreach my $score (@scores) {
        &$perUser($event, $score);
        $score = $score->[0];
    }
    return @scores;
}

sub generateTip {
    my $self = shift;
    my($answer, $tipID, $maxTips) = @_;
    if (length($answer) > $tipID+1) {
        return substr($answer, 0, $tipID+1);
    } else {
        return undef;
    }
}

sub getActivePlayers {
    my $self = shift;
    my($event) = @_;
    my @players;
    if (defined($self->{'currentQuestion'}->{$event->{'channel'}})) { # game in progress
        my $start = $self->{'quizTime'}->{$event->{'channel'}};
        my %players = split(' ', $self->{'players'}->{$event->{'channel'}});
        foreach my $player (keys %players) {
            if ($players{$player} > $start) {
                push(@players, $player);
            }
        }
    }
    return @players;
}

sub stringHash {
    my $self = shift;
    my($string, $key, $value, $multiple) = @_;
    my %hash = split(' ', $$string);
    my @hash;
    if (defined($value)) {
        if (defined($multiple)) {
            $hash{$key} = $hash{$key} * $multiple + $value;
        } else {
            $hash{$key} = $value;
        }
        local $" = ' ';
        @hash = %hash;
        $$string = "@hash";
    } else {
        @hash = %hash;
    }
    return ($hash{$key}, scalar(@hash) / 2);
}