package org.apache.maven.scm.provider.accurev;

/*
 * 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.
 */

import java.io.File;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.maven.scm.ScmVersion;
import org.apache.maven.scm.log.ScmLogger;
import org.apache.maven.scm.provider.ScmProviderRepositoryWithHost;
import org.apache.maven.scm.provider.accurev.util.WorkspaceUtils;
import org.codehaus.plexus.util.StringUtils;

/**
 * 
 */
public class AccuRevScmProviderRepository
    extends ScmProviderRepositoryWithHost
{
    public static final String DEFAULT_TAG_FORMAT = "%s";

    private AccuRev accurev;

    private String streamName;

    private String projectPath;

    private String tagFormat = DEFAULT_TAG_FORMAT;

    private ScmLogger logger;

    public AccuRevScmProviderRepository()
    {
        super();
        // True is a more sensible default (ie for tck tests)
        // TODO raise jira so tck tests properly handle setPersist
        setPersistCheckout( true );

        setShouldUseExportForNonPersistentCheckout( true );
    }

    public String getTagFormat()
    {
        return tagFormat;
    }

    public void setTagFormat( String tagFormat )
    {
        if ( tagFormat == null || !tagFormat.contains( "%s" ) )
        {
            throw new IllegalArgumentException( "tagFormat must contain '%s' to be replaced" );
        }
        this.tagFormat = tagFormat;
    }

    public String getStreamName()
    {
        return streamName;
    }

    public void setStreamName( String streamName )
    {
        this.streamName = streamName;
    }

    public String getProjectPath()
    {
        return projectPath;
    }

    public void setProjectPath( String projectPath )
    {
        this.projectPath = projectPath;
        setCheckoutRelativePath( projectPath );
    }

    public AccuRev getAccuRev()
    {
        return this.accurev;
    }

    public void setAccuRev( AccuRev accurev )
    {
        this.accurev = accurev;
    }

    /**
     * @param info
     * @return true if info indicates a root of the workspace.
     */
    public boolean isWorkSpaceRoot( AccuRevInfo info )
    {
        String p = getProjectPath();
        return ( p != null && WorkspaceUtils.isSameFile( info.getBasedir(), new File( info.getTop(), p ) ) )
            || isWorkSpaceTop( info );
    }

    public boolean isWorkSpaceTop( AccuRevInfo info )
    {
        return info.isWorkSpaceTop();      

    }

    String tagToStream( String tagName )
    {
        return String.format( getTagFormat(), tagName );
    }

    String streamToTag( String streamName )
    {
        tagFormat = getTagFormat();
        // TODO - strictly we should quote either side of the %s
        String tagPatternString = tagToStream( "(.*)" );
        Pattern tagPattern = Pattern.compile( tagPatternString );

        Matcher tagMatcher = tagPattern.matcher( streamName );
        if ( tagMatcher.matches() )
        {
            return tagMatcher.group( 1 );
        }
        else
        {
            return streamName;
        }

    }

    public void setLogger( ScmLogger logger )
    {
        this.logger = logger;
    }

    // TODO raise JIRA to pull up these methods to ScmProviderRepository

    private String checkoutRelativePath;

    private boolean shouldUseExportForNonPersistentCheckout = true;

    /**
     * The relative path of the directory of the checked out project in comparison to the checkout directory, or an
     * empty String in case the checkout directory equals the project directory.
     * <p/>
     * With most SCMs, this is just an empty String, meaning that the checkout directory equals the project directory.
     * But there are cases (e.g. ClearCase) where within the checkout directory, the directory structure of the SCM
     * system is repeated. E.g. if you check out the project "my/project" to "/some/dir", the project sources are
     * actually checked out to "some/dir/my/project". In this example, relativePathProjectDirectory would contain
     * "my/project".
     */
    public String getCheckoutRelativePath()
    {
        if ( this.checkoutRelativePath == null )
        {
            return "";
        }
        return this.checkoutRelativePath;
    }

    public void setCheckoutRelativePath( String checkoutRelativePath )
    {
        this.checkoutRelativePath = checkoutRelativePath;
    }

    /**
     * Relative project path for export
     * 
     * @return default same as {@link #getCheckoutRelativePath()}
     */
    public String getExportRelativePath()
    {
        return getCheckoutRelativePath();
    }

    /**
     * When checkout is not expected to be refreshed or committed, should export be used instead? Perforce, Clearcase
     * and AccuRev store their meta-data about file status within the server rather than files in the source tree. This
     * makes checkouts within checkouts (eg release:perform) difficult. Typically there is a way to do a lightweight
     * export instead which can be implemented as the "export" command. This is a hint to downstream applications that
     * "export" is available and should be used in preference to "checkout" in cases where "update" and "commit" are not
     * intended to be used. (ie release:perform)
     * 
     * @return false by default
     */
    public boolean shouldUseExportForNonPersistentCheckout()
    {
        return this.shouldUseExportForNonPersistentCheckout;
    }

    public void setShouldUseExportForNonPersistentCheckout( boolean shouldUseExportForNonPersistentCheckout )
    {
        this.shouldUseExportForNonPersistentCheckout = shouldUseExportForNonPersistentCheckout;
    }

    public String getDepotRelativeProjectPath()
    {
        return "/./" + ( projectPath == null ? "" : projectPath );
    }

    public AccuRevVersion getAccuRevVersion( ScmVersion scmVersion )
    {

        String tran = null;
        String basisStream = null;

        if ( scmVersion == null )
        {
            basisStream = getStreamName();
        }
        else
        {
            String name = StringUtils.clean( scmVersion.getName() );

            String[] versionComponents = name.split( "[/\\\\]", 2 );
            basisStream = versionComponents[0];
            if ( basisStream.length() == 0 )
            {
                // Use the default stream from the URL
                basisStream = getStreamName();
            }
            else
            {
                // name is a tag name - convert to a stream.
                basisStream = tagToStream( basisStream );
            }

            if ( versionComponents.length == 2 && versionComponents[1].length() > 0 )
            {
                tran = versionComponents[1];
            }
        }

        return new AccuRevVersion( basisStream, tran );
    }

    public String getSnapshotName( String tagName )
    {
        return tagToStream( tagName );
    }

    public String getRevision( String streamName, Date date )
    {
        return getRevision( streamName, AccuRev.ACCUREV_TIME_SPEC.format( date == null ? new Date() : date ) );
    }

    public String getRevision( String stream, long fromTranId )
    {
        return getRevision( stream, Long.toString( fromTranId ) );
    }

    public String getRevision( String streamName, String transaction )
    {
        return streamToTag( streamName ) + "/" + transaction;
    }

    public String getWorkSpaceRevision( String workspace )
        throws AccuRevException
    {
        return getRevision( workspace, Long.toString( getCurrentTransactionId( workspace ) ) );
    }

    public Transaction getDepotTransaction( String stream, String tranSpec )
        throws AccuRevException
    {

        if ( tranSpec == null )
        {
            tranSpec = "now";
        }

        List<Transaction> transactions = getAccuRev().history( stream, tranSpec, null, 1, true, true );

        if ( transactions == null || transactions.isEmpty() )
        {
            logger.warn( "Unable to find transaction for tranSpec=" + tranSpec );
            return null;
        }
        else
        {
            return transactions.get( 0 );
        }

    }

    public String getDepotTransactionId( String stream, String tranSpec )
        throws AccuRevException
    {
        Transaction t = getDepotTransaction( stream, tranSpec );

        return t == null ? tranSpec : Long.toString( t.getTranId() );
    }

    private long getCurrentTransactionId( String workSpaceName )
        throws AccuRevException
    {
        // AccuRev does not have a way to get at this workspace info by name.
        // So we have to do it the hard way...

        AccuRev accuRev = getAccuRev();

        Map<String, WorkSpace> workSpaces = accuRev.showWorkSpaces();

        WorkSpace workspace = workSpaces.get( workSpaceName );

        if ( workspace == null )
        {
            // Must be a reftree
            workSpaces = accuRev.showRefTrees();
            workspace = workSpaces.get( workSpaceName );
        }

        if ( workspace == null )
        {
            throw new AccuRevException( "Can't find workspace " + workSpaceName );
        }
        return workspace.getTransactionId();
    }

    public String toString()
    {
        StringBuilder buff = new StringBuilder( "AccuRevScmProviderRepository" );
        buff.append( " user=" );
        buff.append( getUser() );
        buff.append( " pass=" );
        buff.append( getPassword() == null ? "null" : StringUtils.repeat( "*", getPassword().length() ) );
        buff.append( " host=" );
        buff.append( getHost() );
        buff.append( " port=" );
        buff.append( getPort() );
        buff.append( " stream=" );
        buff.append( getStreamName() );
        buff.append( " projectPath=" );
        buff.append( getProjectPath() );

        return buff.toString();
    }

    public static String formatTimeSpec( Date when )
    {

        if ( when == null )
        {
            return "now";
        }

        return AccuRev.ACCUREV_TIME_SPEC.format( when );

    }

}
