#------------------------------------------------------------
# Copyright (C) 2006 RoyaltyShare, Inc.   All Rights Reserved
# $Id$
#------------------------------------------------------------
package MediaServe::File;

use strict;
use Data::Dumper;
use Crypt::CBC;

use lib '/app/tools/common/lib';
use Common::Assert;
use Common::XMLObject;
use Common::RSApp;
use RSApache::DBSession;

use lib '/app/tools/mediaserve/lib';
use MediaServe::DB::Item::Dataroot;
use MediaServe::DB::Item::StorageHost;

use constant kCryptKey => 'Fehdud86EsPP';

# !!! Going to make this an XMLObject, mostly for debugging purposes.
#
use base 'Common::XMLObject';

# Global hashes to cache stuff.
#
my %gDatarootCache;
my %gStorageHostCache;

# This is the 'initialization vector', used to salt the encryption algorithm.
#
my $gIV = pack( "LL", 324, 11721 );

#my $gIVHexString = '4dd0eba9463a64d9';

sub _init {
    my ( $self, %args ) = @_;

    my $type = $args{type} || $^O;

    die("Unknow OS Type: '$type'") unless ( $type =~ /(mswin32|windows|linux|darwin)/i );

    $self->{_type} = $type;

    if ( $args{token} ) {
        return $self->_initFromToken( $args{token} );
    }

    #    elsif ($args{id} && $args{expirationDate} && $args{clientID} && $args{sessionID})
    elsif ( $args{id} ) {
        return $self->_initFromArgs(%args);
    } else {
        assert( 0, "INCORRECT ARGS: " . Dumper( \%args ) );
    }

    # !!! Will probably want additional ways to instantiate these objects.
    #
    return $self;
}

sub _initFromToken {
    my ( $self, $token ) = @_;
    assert($token);

    $self->{Token} = $token;
    return $self;
}

sub _initFromArgs {
    my ( $self, %args ) = @_;

    assert( $args{id} );

    $self->{ID} = $args{id};

    $self->{ClientID} = Common::RSApp::GetClientID();

    #    $self->{ClientID} = $args{clientID};

    # These are optional.
    # We'll apply the defaults lazily if necessary.
    #
    $self->{ExpirationDate} = $args{expirationDate} if ( $args{expirationDate} );
    $self->{SessionID}      = $args{sessionID}      if ( $args{sessionID} );

    return $self;
}

sub logicalVolume {
    my ($self) = @_;

    my $datarootID = $self->datarootID();

    #  We'll keep this stuff cached in the class.
    #  If I have never seen this ID, then I'll attempt
    #  to read it from the client database.
    #
    if ( !$gDatarootCache{$datarootID} ) {
        my $datarootObj = MediaServe::DB::Item::Dataroot->Lookup( dataroot_id => $datarootID );
        die "ERROR - unknown dataroot_id $datarootID" unless $datarootObj;
        $gDatarootCache{$datarootID} = {};

        $gDatarootCache{$datarootID}->{windows} = $datarootObj->windows_mount_point;
        $gDatarootCache{$datarootID}->{linux}   = $datarootObj->mount_point;
    }

    # This is just the mount point, ie 'vol001' or something like that.
    #
    # We need to know what operating system we are running so we can return the correct
    # mount point
    if ( $self->{_type} =~ /mswin32|windows/i ) {
        return $gDatarootCache{$datarootID}->{windows};
    } elsif ( $self->{_type} =~ /linux/i ) {
        return $gDatarootCache{$datarootID}->{linux};
    } else {
        die "Unknown Operating System";
    }
}

sub hostname {
    my ($self) = @_;

    # Only the _production_ boxes will return the 'real' hostname.
    # Everything else will just get back your hostname.
    # This is to provide a measure of protection around the live data.
    #
    if ( Common::RSApp::IsProductionServer() ) {
        my $storageHostID = $self->storageHostID();

        #  We'll keep this stuff cached in the class.
        #  If I have never seen this ID, then I'll attempt
        #  to read it from the client database.
        #
        if ( !$gStorageHostCache{$storageHostID} ) {
            my $obj = MediaServe::DB::Item::StorageHost->Lookup( storage_host_id => $storageHostID );
            die "ERROR - unknown storage_host_id $storageHostID" unless $obj;
            $gStorageHostCache{$storageHostID} = $obj->hostname;
        }

        return $gStorageHostCache{$storageHostID};
    }

    return 'localhost';

}

# The 'relative path' is the path that describes the directory in which
# the file can be found.
# So, something like 'img/14/000/000/000/'
#
sub relativePath {
    my ($self) = @_;

    if ( !$self->{RelativePath} ) {
        $self->_deriveRelativePath();
    }
    return $self->{RelativePath};
}

sub filename {
    my ($self) = @_;

    if ( !$self->{FileName} ) {
        $self->_deriveFileName();
    }
    return $self->{FileName};
}

sub token {
    my ($self) = @_;

    if ( !$self->{Token} ) {
        $self->_deriveToken();
    }
    return $self->{Token};
}

sub id {
    my ($self) = @_;
    if ( !$self->{ID} ) {
        $self->_deriveID();
    }
    return $self->{ID};
}

sub datarootID {
    my ($self) = @_;

    if ( !$self->{DatarootID} ) {
        $self->_deriveDatarootID();
    }
    return $self->{DatarootID};
}

sub storageHostID {
    my ($self) = @_;

    if ( !$self->{StorageHostID} ) {
        $self->_deriveStorageHostID();
    }
    return $self->{StorageHostID};
}

sub expirationDate {
    my ($self) = @_;
    if ( !$self->{ExpirationDate} ) {
        $self->_deriveExpirationDate();
    }
    return $self->{ExpirationDate};
}

sub clientID {
    my ($self) = @_;
    if ( !$self->{ClientID} ) {
        $self->_deriveClientID();
    }
    return $self->{ClientID};
}

sub sessionID {
    my ($self) = @_;
    if ( !$self->{SessionID} ) {
        $self->_deriveSessionID();
    }
    return $self->{SessionID};
}

# !!! We'll implement this in subclasses instead.
sub __url {
    my ($self) = @_;

    #    my $url = 'http://' . $self->hostname() . '/' . $self->mediaType() . '/' . $self->basePath() . '/' . $self->token();

    # Lets simplify this for now
    # We can always expand the path later.
    #
    my $url = 'http://media.royaltyshare.com/media' . $self->mediaType() . '/' . $self->token();
    return $url;
}

sub mediaType {
    my ($self) = @_;

    # This is 'img', 'mp3', etc : the base of the url path.
    #
    assert( 0, 'override this' );
}

# Going to use the same generic scheme everywhere.
# Let's assume a 999 item limit per subdirectory.
# And further, we'll assume each id will fit inside a 32-bit unsigned long.
#
sub _deriveRelativePath {
    my ($self) = @_;
    my $id = $self->id();

    my @c = split( //, sprintf( "%012u", $id ) );

    $self->{RelativePath} =
        $self->mediaType() . '/'
      . $self->clientID() . '/'
      . $c[0]
      . $c[1]
      . $c[2] . '/'
      . $c[3]
      . $c[4]
      . $c[5] . '/'
      . $c[6]
      . $c[7]
      . $c[8];
}

sub _deriveSessionID {
    assert( 0, 'override this' );
}

sub _deriveClientID {
    assert( 0, 'override this' );
}

sub _deriveToken {
    assert( 0, 'override this' );
}

sub _deriveFileName {
    assert( 0, 'override this' );
}

sub _deriveID {
    assert( 0, 'override this' );
}

sub _deriveExpirationDate {
    assert( 0, 'override this' );
}

sub _deriveStorageHostID {
    assert( 0, 'override this' );
}

sub _deriveDatarootID {
    assert( 0, 'override this' );
}

sub fileType {
    assert( 0, 'override this' );
}

sub Encrypt {
    my ( $class, $unencrypted ) = @_;

    my $cipher = Crypt::CBC->new( -key => kCryptKey, -cipher => 'Blowfish', -header => 'none', -iv => $gIV );
    my $cipherdata = $cipher->encrypt($unencrypted);

    # $cipherdata is just a buffer with bytes in it.
    # We'll create a hex string.
    my $unpacked = unpack( "H*", $cipherdata );

    return $unpacked;
}

sub Decrypt {
    my ( $class, $encrypted ) = @_;

    my $cipher = Crypt::CBC->new( -key => kCryptKey, -cipher => 'Blowfish', -header => 'none', -iv => $gIV );

    # The encrypted data is in the form of a hex string. Turn it back into 'raw' data.
    #
    my $pack = pack( "H*", $encrypted );

    my $decoded = $cipher->decrypt($pack);
    return $decoded;
}
1;
