#**************************************************************
#  
#  Licensed to the Apache Software Foundation (ASF) under one
#  or more contributor license agreements.  See the NOTICE file
#  distributed with this work for additional information
#  regarding copyright ownership.  The ASF licenses this file
#  to you under the Apache License, Version 2.0 (the
#  "License"); you may not use this file except in compliance
#  with the License.  You may obtain a copy of the License at
#  
#    http://www.apache.org/licenses/LICENSE-2.0
#  
#  Unless required by applicable law or agreed to in writing,
#  software distributed under the License is distributed on an
#  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
#  KIND, either express or implied.  See the License for the
#  specific language governing permissions and limitations
#  under the License.
#  
#**************************************************************




#
# CwsConfig.pm - package for read CWS config data
# 

package CwsConfig;
use strict;

use Carp;
use URI::Escape;

##### ctor ####

sub new 
{
    my $invocant = shift;
    my $class = ref($invocant) || $invocant;
    my $self = {};
    $self->{_CONFIG_FILE}        = undef;    # config file
    $self->{_GLOBAL}             = undef;    # is it a global config file?
    $self->{VCSID}               = undef;    # VCSID
    $self->{CWS_DB_URL_LIST_REF} = undef;    # list of CWS DB servers
    $self->{NET_PROXY}           = undef;    # network proxy
    $self->{CWS_SERVER_ROOT}     = undef;    # cvs server
    $self->{CWS_MIRROR_ROOT}     = undef;    # mirror of cvs server
    $self->{CWS_LOCAL_ROOT}      = undef;    # local cvs server
    $self->{PUBLIC_SVN_SERVER}   = undef;    # public svn server
    $self->{PRIVATE_SVN_SERVER}  = undef;    # private svn server
    bless ($self, $class);
    return $self;
}

sub vcsid
{
    my $self = shift;
    
    if ( !defined($self->{VCSID}) ) {
        # environment overrides config file
        my $vcsid = $ENV{VCSID};
        if ( !defined($vcsid) ) {
            # check config file
            my $config_file = $self->get_config_file();
            $vcsid = $config_file->{CWS_CONFIG}->{'CVS_ID'};
            if ( !defined($vcsid) ) {
                # give up
                croak("ERROR: no CVS_ID entry found in '\$HOME/.cwsrc'.\n" );
            }
        }
        $self->{VCSID} = $vcsid;
    }
    return $self->{VCSID};
}

sub cws_db_url_list_ref
{
    my $self = shift;
    
    if ( !defined($self->{CWS_DB_URL_LIST_REF}) ) {
        my $config_file = $self->get_config_file();

        my $i = 1;
        my @cws_db_servers;

        while ( 1 ) {
            my $val = $config_file->{CWS_CONFIG}->{"CWS_DB_SERVER_$i"};
            last if !defined($val);
            push(@cws_db_servers, $val);
            $i++;
        }

        if ( !@cws_db_servers) {
            croak("ERROR: no CWS_DB_SERVER_* entry found in '\$HOME/.cwsrc'.\n" );
        }
    
        if ( $cws_db_servers[0] =~ /^https:\/\// ) {
            my $id = $self->vcsid();
            my $password = $config_file->{CWS_CONFIG}->{'CVS_PASSWORD'};

            if ( !defined($password) ) {
                croak("ERROR: no CVS_PASSWORD entry found in '\$HOME/.cwsrc'.\n" );
            }
            
            # *i49473* - do not accept scrambled passwords ending with a space
            if ( $password =~ / $/) {
                croak("ERROR: The (scrambled) CVS_PASSWORD ends with a space. This is known to cause problems when connecting to the OpenOffice.org EIS database. Please change your OOo account's password" );
            }

            # We are going to stuff $id and $password in an URL, do proper escaping.
            $id = uri_escape($id);
            $password = uri_escape($password);
        
            foreach ( @cws_db_servers ) {
                s/^https:\/\//https:\/\/$id:$password@/;
            }
        }
        
        $self->{CWS_DB_URL_LIST_REF} = \@cws_db_servers;
    }
    return $self->{CWS_DB_URL_LIST_REF};
}

sub net_proxy
{
    my $self = shift;
    
    if ( !defined($self->{NET_PROXY}) ) {
        my $config_file = $self->get_config_file();
        my $net_proxy = $config_file->{CWS_CONFIG}->{'PROXY'};
        if ( !defined($net_proxy) ) {
            $net_proxy = "";
        }
        $self->{NET_PROXY} = $net_proxy;
    }
    return $self->{NET_PROXY} ? $self->{NET_PROXY} : undef;
}

sub cvs_binary
{
    my $self = shift;
    
    if ( !defined($self->{CVS_BINARY}) ) {
        my $config_file = $self->get_config_file();
        my $cvs_binary = $config_file->{CWS_CONFIG}->{'CVS_BINARY'};
        if ( !defined($cvs_binary) ) {
            # defaults
            $cvs_binary = ($^O eq 'MSWin32') ? 'cvs.exe' : 'cvs';
        }
        # special case, don't ask
        if ( $self->{_GLOBAL} && $cvs_binary =~ /cvs.clt2/ && $^O eq 'MSWin32' ) {
            $cvs_binary = 'cvsclt2.exe';
        }
        $self->{CVS_BINARY} = $cvs_binary;
    }
    return $self->{CVS_BINARY};
}

sub cvs_server_root
{
    my $self = shift;
    
    if ( !defined($self->{CVS_SERVER_ROOT}) ) {
        my $config_file = $self->get_config_file();
        my $cvs_server_root = $config_file->{CWS_CONFIG}->{'CVS_SERVER_ROOT'};
        if ( !defined($cvs_server_root) ) {
            # give up, this is a mandatory entry
            croak("ERROR: can't parse CVS_SERVER_ROOT entry in '\$HOME/.cwsrc'.\n");
        }
        if ( $self->{_GLOBAL} ) {
            # a global config file will almost always have the wrong vcsid in
            # the cvsroot -> substitute vcsid
            my $id = $self->vcsid();
            $cvs_server_root =~ s/:pserver:\w+@/:pserver:$id@/;
        }
        $self->{CVS_SERVER_ROOT} = $cvs_server_root;
    }
    return $self->{CVS_SERVER_ROOT};
}

sub cvs_mirror_root
{
    my $self = shift;
    
    if ( !defined($self->{CVS_MIRROR_ROOT}) ) {
        my $config_file = $self->get_config_file();
        my $cvs_mirror_root = $config_file->{CWS_CONFIG}->{'CVS_MIRROR_ROOT'};
        if ( !defined($cvs_mirror_root) ) {
            $cvs_mirror_root = "";
        }
        $self->{CVS_MIRROR_ROOT} = $cvs_mirror_root;
    }
    return $self->{CVS_MIRROR_ROOT} ? $self->{CVS_MIRROR_ROOT} : undef;
}

sub cvs_local_root
{
    my $self = shift;
    
    if ( !defined($self->{CVS_LOCAL_ROOT}) ) {
        my $config_file = $self->get_config_file();
        my $cvs_local_root = $config_file->{CWS_CONFIG}->{'CVS_LOCAL_ROOT'};
        if ( !defined($cvs_local_root) ) {
            $cvs_local_root = "";
        }
        $self->{CVS_LOCAL_ROOT} = $cvs_local_root;
    }
    return $self->{CVS_LOCAL_ROOT} ? $self->{CVS_LOCAL_ROOT} : undef;
}

sub get_cvs_server
{
    my $self = shift;

    my ($method, $id, $server, $repository) = CwsConfig::split_root($self->cvs_server_root(), 'SERVER');
    return $server;
}

sub get_cvs_mirror
{
    my $self = shift;

    my ($method, $id, $server, $repository) = CwsConfig::split_root($self->cvs_mirror_root(), 'MIRROR');
    return $server;
}

sub get_cvs_local
{
    my $self = shift;

    my ($method, $id, $server, $repository) = CwsConfig::split_root($self->cvs_local_root(), 'LOCAL');
    return $server;
}

sub get_cvs_server_method
{
    my $self = shift;

    my ($method, $id, $server, $repository) = CwsConfig::split_root($self->cvs_server_root(), 'SERVER');
    return $method;
}

sub get_cvs_mirror_method
{
    my $self = shift;

    my ($method, $id, $server, $repository) = CwsConfig::split_root($self->cvs_mirror_root(), 'MIRROR');
    return $method;
}

sub get_cvs_local_method
{
    my $self = shift;

    my ($method, $id, $server, $repository) = CwsConfig::split_root($self->cvs_local_root(), 'LOCAL');
    return $method;
}

sub get_cvs_server_repository
{
    my $self = shift;

    my ($method, $id, $server, $repository) = CwsConfig::split_root($self->cvs_server_root(), 'SERVER');
    return $repository;
}

sub get_cvs_mirror_repository
{
    my $self = shift;

    my ($method, $id, $server, $repository) = CwsConfig::split_root($self->cvs_mirror_root(), 'MIRROR');
    return $repository;
}

sub get_cvs_local_repository
{
    my $self = shift;

    my ($method, $id, $server, $repository) = CwsConfig::split_root($self->cvs_local_root(), 'LOCAL');
    return $repository;
}

sub get_cvs_server_id
{
    my $self = shift;

    my ($method, $id, $server, $repository) = CwsConfig::split_root($self->cvs_server_root(), 'SERVER');
    return $id;
}

sub get_cvs_mirror_id
{
    my $self = shift;

    my ($method, $id, $server, $repository) = CwsConfig::split_root($self->cvs_mirror_root(), 'MIRROR');
    return $id;
}

sub get_cvs_local_id
{
    my $self = shift;

    my ($method, $id, $server, $repository) = CwsConfig::split_root($self->cvs_local_root(), 'LOCAL');
    return $id;
}

#### SVN methods ####

sub get_ooo_svn_server
{
    my $self = shift;
    
    if ( !defined($self->{SVN_SERVER}) ) {
        my $config_file = $self->get_config_file();
        my $ooo_svn_server = $config_file->{CWS_CONFIG}->{'SVN_SERVER'};
        if ( !defined($ooo_svn_server) ) {
            $ooo_svn_server = "";
        }
        $self->{SVN_SERVER} = $ooo_svn_server;
    }
    return $self->{SVN_SERVER} ? $self->{SVN_SERVER} : undef;
}

sub get_so_svn_server
{
    my $self = shift;
    
    if ( !defined($self->{SO_SVN_SERVER}) ) {
        my $config_file = $self->get_config_file();
        my $so_svn_server = $config_file->{CWS_CONFIG}->{'SO_SVN_SERVER'};
        if ( !defined($so_svn_server) ) {
            $so_svn_server = "";
        }
        $self->{SO_SVN_SERVER} = $so_svn_server;
    }
    return $self->{SO_SVN_SERVER} ? $self->{SO_SVN_SERVER} : undef;
}

#### HG methods ####

sub _get_hg_source
{
    my $self               = shift;
    my $repository_source  = shift;
    if ( !defined($self->{$repository_source}) ) {
        my $config_file = $self->get_config_file();
        my $source = $config_file->{CWS_CONFIG}->{$repository_source};
        if ( !defined($source) ) {
            $source = "";
        }
        $self->{$repository_source} = $source;
    }
    return $self->{$repository_source} ? $self->{$repository_source} : undef;

}

sub get_hg_source
{
    my $self        = shift;
    my $repository  = shift;
    my $location    = shift;

    #Special prefix handling, see cwsrc
    if ($repository eq "OOO")
    {
        if ($location eq "LOCAL")
        {
            return $self->_get_hg_source('HG_LOCAL_SOURCE');
        }
        elsif ($location eq "LAN")
        {
            return $self->_get_hg_source('HG_LAN_SOURCE');
        }
        elsif ($location eq "REMOTE")
        {
            return $self->_get_hg_source('HG_REMOTE_SOURCE');
        }
    }
    else
    {
        if ($location eq "LOCAL")
        {
            return $self->_get_hg_source($repository.'_HG_LOCAL_SOURCE');
        }
        elsif ($location eq "LAN")
        {
            return $self->_get_hg_source($repository.'_HG_LAN_SOURCE');
        }
        elsif ($location eq "REMOTE")
        {
            return $self->_get_hg_source($repository.'_HG_REMOTE_SOURCE');
        }
    }
}

#### Prebuild binaries configuration ####

sub get_prebuild_binaries_location
{
    my $self = shift;
    
    if ( !defined($self->{PREBUILD_BINARIES}) ) {
        my $config_file = $self->get_config_file();
        my $pre_build_binaries = $config_file->{CWS_CONFIG}->{'PREBUILD_BINARIES'};
        if ( !defined($pre_build_binaries) ) {
            $pre_build_binaries = "";
        }
        $self->{PREBUILD_BINARIES} = $pre_build_binaries;
    }
    return $self->{PREBUILD_BINARIES} ? $self->{PREBUILD_BINARIES} : undef;
}



#### class methods #####
sub get_config
{
    my $config = CwsConfig->new();
    return $config;
}

sub split_root
{
    my $root = shift;
    my $type = shift;

    if ( !defined($root) ) {
        return (undef, undef, undef, undef);
    }

    my ($dummy, $method, $id_at_host, $repository) = split(/:/, $root);
    $repository =~ s/^\d*//;
    my ($id, $server);
    if ( $id_at_host ) {
        ($id, $server) = split(/@/, $id_at_host);
    }
    if ( !defined($method) || !defined($id) || !defined($server) || !defined($repository) ) {
        # give up
        print  "$method, $id, $server, $repository\n";
        croak("ERROR: can't parse CVS_".$type."_ROOT entry in '\$HOME/.cwsrc'.\n");
    }
    return ($method, $id, $server, $repository);
}

#### private helper methods ####

sub get_config_file
{
    my $self = shift;

    if ( !defined $self->{_CONFIG_FILE} ) {
        $self->parse_config_file();
    }
    return $self->{_CONFIG_FILE};
}

sub read_config
{
    my $self = shift;
    my $fname = shift;
    my $fhandle;
    my $section = '';
    my %config;

    open ($fhandle, $fname) || croak("ERROR: Can't open '$fname': $!");
    while ( <$fhandle> ) {
    	tr/\r\n//d;   # win32 pain
        # Issue #i62815#: Scrambled CVS passwords may contain one or more '#'.
        # Ugly special case needed: still allow in-line (perl style) comments 
        # elsewhere because existing configuration files may depend on them.
        if ( !/^\s*CVS_PASSWORD/ ) {
	        s/\#.*//; # kill comments
        }
    	/^\s*$/ && next;

	    if (/\[\s*(\S+)\s*\]/) {
	        $section = $1;
    	    if (!defined $config{$section}) {
	    	    $config{$section} = {};
    	    }
	    }
	    defined $config{$section} || croak("ERROR: unknown / no section '$section'\n");
    	if ( m/(\w[\w\d]*)=(.*)/ ) {
            my $var = $1;
            my $val = $2;
            # New style value strings may be surrounded by quotes
            if ( $val =~ s/\s*(['"])(.*)\1\s*$/$2/ ) {
                my $quote = $1;
                # If and only if the value string is surrounded by quotes we
                # can expect that \" or \' are escaped characters. In an unquoted
                # old style value string they could mean exactly what is standing there
                #
                # Actually the RE above works without quoting the quote character 
                # (either " or ') inside the value string but users will probably 
                # expect that they need to be escaped if quotes are used.
                #
                # This is still not completly correct for all thinkable situations but 
                # should be good enough for all practical use cases.
    		    $val =~ s/\\($quote)/$1/g;
            }
            $config{$section}->{$var} = $val;
            # print "Set '$var' to '$val'\n";
	    }
    }
    close ($fhandle) || croak("ERROR: Failed to close: $!");

    $self->{_CONFIG_FILE} = \%config;
}

sub parse_config_file
{
    my $self = shift;
    
    my $config_file;
    # check for config files
    if ( -e "$ENV{HOME}/.cwsrc" ) {
	$self->read_config("$ENV{HOME}/.cwsrc");
        $self->{_GLOBAL} = 0;
    }
    elsif ( -e "$ENV{COMMON_ENV_TOOLS}/cwsrc" ) {
        $self->read_config("$ENV{COMMON_ENV_TOOLS}/cwsrc");
        $self->{_GLOBAL} = 1;
    }
    else {
        croak("ERROR: can't find CWS config file '\$HOME/.cwsrc'.\n");
    }
}

sub sointernal
{
	my $self = shift;
	my $config_file = $self->get_config_file();
	my $val = ($config_file->{CWS_CONFIG}->{"SO_INTERNAL"}) ? 1 : 0;
	return $val;
}
1; # needed by "use" or "require"
