/**
 * The contents of this file are subject to the Mozilla Public License Version 1.1
 * (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.mozilla.org/MPL/
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for the
 * specific language governing rights and limitations under the License.
 *
 * The Original Code is "SegmentFinder.java".  Description:
 * "A tool for getting segments by name within a message or part of a message."
 *
 * The Initial Developer of the Original Code is University Health Network. Copyright (C)
 * 2002.  All Rights Reserved.
 *
 * Contributor(s): ______________________________________.
 *
 * Alternatively, the contents of this file may be used under the terms of the
 * GNU General Public License (the "GPL"), in which case the provisions of the GPL are
 * applicable instead of those above.  If you wish to allow use of your version of this
 * file only under the terms of the GPL and not to allow others to use your version
 * of this file under the MPL, indicate your decision by deleting  the provisions above
 * and replace  them with the notice and other provisions required by the GPL License.
 * If you do not delete the provisions above, a recipient may use your version of
 * this file under either the MPL or the GPL.
 *
 */

package ca.uhn.hl7v2.util;

import ca.uhn.hl7v2.model.*;
import ca.uhn.hl7v2.HL7Exception;
import java.util.regex.*;

/**
 * A tool for getting segments by name within a message or part of a message.
 * @author Bryan Tripp
 */
public class SegmentFinder extends MessageNavigator {

    private static final Pattern VALID_PATTERN_PATTERN = Pattern.compile("[\\w\\*\\?]*");
    private static final Pattern LITERAL_UNBOUNDED = Pattern.compile("\\*");
    private static final Pattern LITERAL_OPTIONAL = Pattern.compile("\\?");

    /**
     * Creates a new instance of SegmentFinder.
     * @param root the scope of searches -- may be a whole message or only a branch
     */
    public SegmentFinder(Group root) {
        super(root);
    }
    
    /**
     * Returns the first segment with a name that matches the given pattern, in a depth-first search.  
     * Repeated searches are initiated from the location just AFTER where the last segment was found.
     * Call reset() is this is not desired.  Note: this means that the current location will not be found.
     * @param namePattern the name of the segment to find.  The wildcard * means any number
     *      of arbitrary characters; the wildcard ? one arbitrary character
     *      (eg "P*" or "*ID" or "???" or "P??" would match on PID).
     * @param rep the repetition of the segment to return
     */
    public Segment findSegment(String namePattern, int rep) throws HL7Exception {
        Structure s;
        do {
            s = findStructure(namePattern, rep);
        } while (!Segment.class.isAssignableFrom(s.getClass()));
        return (Segment) s;
    }
    
    /**
     * As findSegment(), but will only return a group.
     */
    public Group findGroup(String namePattern, int rep) throws HL7Exception {
        Structure s;
        do {
            s = findStructure(namePattern, rep);
        } while (!Group.class.isAssignableFrom(s.getClass()));
        return (Group) s;
    }
    
    /**
     * Returns the first matching structure AFTER the current position
     */
    private Structure findStructure(String namePattern, int rep) throws HL7Exception {
        Structure s = null;
        
        while (s == null) {
            String currentNameInParent = iterate(false, false);
            String currentName = getCurrentStructure(0).getName();
            if (matches(namePattern, currentName) || matches(namePattern, currentNameInParent)) {
                s = getCurrentStructure(rep);
            }
        }
        return s;
    }
    
    /**
     * Returns the first segment with a name matching the given pattern that is a sibling of
     * the structure at the current location.  Other parts of the message are
     * not searched (in contrast to findSegment).
     * As a special case, if the pointer is at the root, the children of the root
     * are searched.
     * @param namePattern the name of the segment to get.  The wildcad * means any number
     *      of arbitrary characters; the wildard ? one arbitrary character
     *      (eg "P*" or "*ID" or "???" or "P??" would match on PID).
     * @param rep the repetition of the segment to return
     */
    public Segment getSegment(String namePattern, int rep) throws HL7Exception {
        Structure s = getStructure(namePattern, rep);
        if (!Segment.class.isAssignableFrom(s.getClass())) {
            throw new HL7Exception(s.getName() + " is not a segment");
        }
        return (Segment) s;
    }
    
    /**
     * As getSegment() but will only return a group.
     */
    public Group getGroup(String namePattern, int rep) throws HL7Exception {
        Structure s = getStructure(namePattern, rep);
        if (!Group.class.isAssignableFrom(s.getClass())) {
            throw new HL7Exception(s.getName() + " is not a group");
        }
        return (Group) s;
    }
    
    private Structure getStructure(String namePattern, int rep) throws HL7Exception {
        Structure s = null;
        
        if (getCurrentStructure(0).equals(this.getRoot()))
            drillDown(0);
        
        String[] names = getCurrentStructure(0).getParent().getNames();
        for (int i = 0; i < names.length && s == null; i++) {
            if (matches(namePattern, names[i])) {
                toChild(i);
                s = getCurrentStructure(rep);
            }
        }
        
        if (s == null)
            throw new HL7Exception("Can't find " + namePattern + " as a direct child");
        
        return s;
    }
    
    /**
     * Tests whether the given name matches the given pattern.
     */
    /*private boolean matches(String pattern, String candidate) {
        boolean matches = false;
        boolean substring = false;
        if (pattern.substring(0, 1).equals("*")) {
            substring = true;
            pattern = pattern.substring(1);
        }
        
        if (substring && (candidate.indexOf(pattern) >= 0)) {
            matches = true;
        } else if (!substring && candidate.equals(pattern)) {
            matches = true;
        }
        return matches;
    }*/
    
    /**
     * Tests whether the given name matches the given pattern.
     */
    private boolean matches(String pattern, String candidate) {
        //shortcut ...
        if (pattern.equals(candidate)) {
            return true;
        }
        if (!VALID_PATTERN_PATTERN.matcher(pattern).matches())
            throw new IllegalArgumentException("The pattern " + pattern + " is not valid.  Only [\\w\\*\\?]* allowed.");
        pattern = LITERAL_UNBOUNDED.matcher(pattern).replaceAll(".*");
        pattern = LITERAL_OPTIONAL.matcher(pattern).replaceAll(".");
        return Pattern.matches(pattern, candidate);
    }
}
