/*
 * 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.
 */
package org.apache.felix.cm.impl.helper;


import org.apache.felix.cm.impl.Activator;
import org.osgi.framework.Bundle;
import org.osgi.framework.ServiceReference;


/**
 * The <code>TargetedPID</code> class represents a targeted PID as read
 * from a configuration object.
 * <p>
 * For a factory configuration the <code>TargetedPID</code> represents
 * the factory PID of the configuration. Otherwise it represents the
 * PID itself of the configuration.
 */
public class TargetedPID
{

    private final String rawPid;

    private final String servicePid;

    private final String symbolicName;
    private final String version;
    private final String location;

    /**
     * The level of binding of this targeted PID:
     * <ul>
     * <li><code>0</code> -- this PID is not targeted at all</li>
     * <li><code>1</code> -- this PID is targeted by the symbolic name</li>
     * <li><code>2</code> -- this PID is targeted by the symbolic name and version</li>
     * <li><code>3</code> -- this PID is targeted by the symoblic name, version, and location</li>
     * </ul>
     */
    private final short bindingLevel;

    public TargetedPID( final String rawPid )
    {
        this.rawPid = rawPid;

        if ( rawPid.indexOf( '|' ) < 0 )
        {
            this.servicePid = rawPid;
            this.symbolicName = null;
            this.version = null;
            this.location = null;
            this.bindingLevel = 0;
        }
        else
        {
            int start = 0;
            int end = rawPid.indexOf( '|' );
            this.servicePid = rawPid.substring( start, end );

            start = end + 1;
            end = rawPid.indexOf( '|', start );
            if ( end >= 0 )
            {
                this.symbolicName = rawPid.substring( start, end );
                start = end + 1;
                end = rawPid.indexOf( '|', start );
                if ( end >= 0 )
                {
                    this.version = rawPid.substring( start, end );
                    this.location = rawPid.substring( end + 1 );
                    this.bindingLevel = 3;
                }
                else
                {
                    this.version = rawPid.substring( start );
                    this.location = null;
                    this.bindingLevel = 2;
                }
            }
            else
            {
                this.symbolicName = rawPid.substring( start );
                this.version = null;
                this.location = null;
                this.bindingLevel = 1;
            }
        }
    }


    /**
     * Returns true if the target of this PID (bundle symbolic name,
     * version, and location) match the bundle registering the referenced
     * service.
     * <p>
     * This method just checks the target not the PID value itself, so
     * this method returning <code>true</code> does not indicate whether
     * the service actually is registered with a service PID equal to the
     * raw PID of this targeted PID.
     * <p>
     * This method also returns <code>false</code> if the service has
     * concurrently been unregistered and the registering bundle is now
     * <code>null</code>.
     *
     * @param reference <code>ServiceReference</code> to the registered
     *      service
     * @return <code>true</code> if the referenced service matches the
     *      target of this PID.
     */
    public boolean matchesTarget( ServiceReference<?> reference )
    {
        // already unregistered
        final Bundle serviceBundle = reference.getBundle();
        if ( serviceBundle == null )
        {
            return false;
        }

        // This is not really targeted
        if ( this.symbolicName == null )
        {
            return true;
        }

        // bundle symbolic names don't match
        if ( !this.symbolicName.equals( serviceBundle.getSymbolicName() ) )
        {
            return false;
        }

        // no more specific target
        if ( this.version == null )
        {
            return true;
        }

        // bundle version does not match

        if ( !this.version.equals( serviceBundle.getVersion().toString() ) )
        {
            return false;
        }

        // assert bundle location match
        return this.location == null || this.location.equals( Activator.getLocation(serviceBundle) );
    }


    /**
     * Gets the raw PID with which this instance has been created.
     * <p>
     * If an actual service PID contains pipe symbols that PID might be
     * considered being targeted PID without it actually being one. This
     * method provides access to the raw PID to allow for such services to
     * be configured.
     */
    public String getRawPid()
    {
        return rawPid;
    }


    /**
     * Returns the service PID of this targeted PID which basically is
     * the targeted PID without the targeting information.
     */
    public String getServicePid()
    {
        return servicePid;
    }


    /**
     * Returns <code>true</code> if this targeted PID binds stronger than
     * the <code>other</code> {@link TargetedPID}.
     * <p>
     * This method assumes both targeted PIDs have already been checked for
     * suitability for the bundle encoded in the targetting.
     *
     * @param other The targeted PID to check whether it is binding stronger
     *      or not.
     * @return <code>true</code> if the <code>other</code> targeted PID
     *      is binding strong.
     */
    boolean bindsStronger( final TargetedPID other )
    {
        return this.bindingLevel > other.bindingLevel;
    }


    @Override
    public int hashCode()
    {
        return this.rawPid.hashCode();
    }


    @Override
    public boolean equals( Object obj )
    {
        if ( obj == null )
        {
            return false;
        }
        else if ( obj == this )
        {
            return true;
        }

        // assume equality if same class and raw PID equals
        if ( this.getClass() == obj.getClass() )
        {
            return this.rawPid.equals( ( ( TargetedPID ) obj ).rawPid );
        }

        // not the same class or different raw PID
        return false;
    }


    @Override
    public String toString()
    {
        return this.rawPid;
    }
}
