# --
# Ticket/Number/HexCheck.pm - a serial ticket number generator
# Copyright (C) 2005 Peter Gervai <grin@grin.hu>
# --
# $Id: HexCheck.pm 87 2005-05-03 16:18:08Z grin $
# --
# This software comes with ABSOLUTELY NO WARRANTY. For details, see
# the enclosed file COPYING for license information (GPL). If you
# did not receive this file, see http://www.gnu.org/licenses/gpl.txt.
# --
# Note: 
# available objects are: ConfigObject, LogObject and DBObject
# 
# Generates sequential hex tickets with CRC-like checksum
# (eg. 00001e,000020,00003d,...)
# --
 
package Kernel::System::Ticket::Number::HexCheck;

use strict;

use vars qw($VERSION);
$VERSION = '$Revision: 87 $';
$VERSION =~ s/^\$.*:\W(.*)\W.+?$/$1/;

sub CreateTicketNr {
    my $Self = shift;
    my $JumpCounter = shift || 0;
    # get needed config options 
    my $SystemID = $Self->{ConfigObject}->Get('SystemID');
    my $CounterLog = $Self->{ConfigObject}->Get('Ticket::CounterLog');
    my $MinSize = $Self->{ConfigObject}->Get('Ticket::NumberGenerator::HexCheck::MinCounterSize') || 5;
    
    # read count
    my $Count = 0;
    if (-f $CounterLog) {
        open (DATA, "< $CounterLog") || die "Can't open $CounterLog: $!";
        my $Line = <DATA>;
        ($Count) = split(/;/, $Line);
        close (DATA);
        if ($Self->{Debug} > 0) {
            $Self->{LogObject}->Log(
                                    Priority => 'debug',
                                    Message => "Read counter from $CounterLog: $Count",
                                   );
        }
    }
    
    # count auto increment ($Count++) in hex
    #$Count++;
    #$Count = $Count + $JumpCounter;
    $Count = sprintf("%x", hex( $Count ) + 1 + $JumpCounter );
    
    # write new count
    if (open (DATA, "> $CounterLog")) {
        flock (DATA, 2) || warn "Can't set file lock ($CounterLog): $!";
        print DATA $Count . "\n";
        close (DATA);
        if ($Self->{Debug} > 0) {
            $Self->{LogObject}->Log(
                                    Priority => 'debug',
                                    Message => "Write counter: $Count",
                                   );
        }
    }
    else {
        $Self->{LogObject}->Log(
                                Priority => 'error',
                                Message => "Can't write $CounterLog: $!",
                               );
        die "Can't write $CounterLog: $!";
    }
    
    # pad to MinSize
    $Count = sprintf( "%0${MinSize}s", $Count );
    
    # create new ticket number
    my $Tn = $SystemID . $Count;
    
    # generate and append checksum
    $Tn = $Self->GenerateId( $Tn );
    
    # Check ticket number. If exists generate new one!
    if ($Self->CheckTicketNr(Tn=>$Tn)) {
        $Self->{LoopProtectionCounter}++;
        if ($Self->{LoopProtectionCounter} >= 1000) {
            # loop protection
            $Self->{LogObject}->Log(
                                    Priority => 'error',
                                    Message => "CounterLoopProtection is now $Self->{LoopProtectionCounter}!".
                                    " Stopped CreateTicketNr()!",
                                   );
            return;
        }
        # create new ticket number again
        $Self->{LogObject}->Log(
                                Priority => 'notice',
                                Message => "Tn ($Tn) exists! Creating new one.",
                               );
        $Tn = $Self->CreateTicketNr($Self->{LoopProtectionCounter});
    }
    return $Tn;
}
# --
sub GetTNByString {
    my $Self = shift;
    my $String = shift || return;
    # get needed config options
    my $SystemID = $Self->{ConfigObject}->Get('SystemID');
    my $TicketHook = $Self->{ConfigObject}->Get('TicketHook');
    my $TicketDivider = $Self->{ConfigObject}->Get('TicketDivider') || ': '; # what's the point to get this? :)
    my $MinSize = $Self->{ConfigObject}->Get('TicketNumberGenerator::HexCheck::MinCounterSize') || 5;
    my $MaxSize = $MinSize + 5;
    # check ticket number
    if ($String =~ /$TicketHook:.?($SystemID[\da-z]{$MinSize,$MaxSize})-FW/i) {
        return $1;
    }
    else {
        if ($String =~ /$TicketHook:.?($SystemID[\da-z]{$MinSize,$MaxSize})/i) {
            return $1;
        }
        else {
            # handle old open DateChecksum tickets too
            if ($String =~ /$TicketHook:.?(\d{8,40})/i) {
                return $1;
            }
            else {
                return;
            }
        }
    }
}
# --
# kind of CRC-4 :) 
# $poly = 0xc;
#
sub GenerateId {
    my @table = (4,9,7,10,2,15,1,12,8,5,11,6,14,3,13,0);
    my ($Self,$s) = @_;
    my $reg = 0xf;
    
    for my $i (0..length($s)-1) {
        my $n = hex(substr($s,$i,1));
        $reg = $reg ^ $n;
        $reg = $table[$reg];
        #printf("R %b %x %i\n", $reg,$reg,$n);
    }
    return uc( $s . sprintf("%X", ~$reg & 0xf) );
}
# --
1;
