/*
 *  @(#)ITFProblemManager.java
 *
 * Copyright (C) 2002-2003 Matt Albrecht
 * groboclown@users.sourceforge.net
 * http://groboutils.sourceforge.net
 *
 *  Part of the GroboUtils package at:
 *  http://groboutils.sourceforge.net
 *
 *  Permission is hereby granted, free of charge, to any person obtaining a
 *  copy of this software and associated documentation files (the "Software"),
 *  to deal in the Software without restriction, including without limitation
 *  the rights to use, copy, modify, merge, publish, distribute, sublicense,
 *  and/or sell copies of the Software, and to permit persons to whom the 
 *  Software is furnished to do so, subject to the following conditions:
 *
 *  The above copyright notice and this permission notice shall be included in 
 *  all copies or substantial portions of the Software. 
 *
 *  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 
 *  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 
 *  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL 
 *  THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 
 *  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 
 *  FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 
 *  DEALINGS IN THE SOFTWARE.
 */
package net.sourceforge.groboutils.pmti.v1.itf;

import net.sourceforge.groboutils.pmti.v1.IIssue;
import net.sourceforge.groboutils.pmti.v1.IAttribute;
import net.sourceforge.groboutils.pmti.v1.IIssueState;
import net.sourceforge.groboutils.pmti.v1.IAttributeSet;
import net.sourceforge.groboutils.pmti.v1.IAttributeInfo;
import net.sourceforge.groboutils.pmti.v1.IIssueTypeInfo;
import net.sourceforge.groboutils.pmti.v1.IEditableIssue;
import net.sourceforge.groboutils.pmti.v1.IListAttribute;
import net.sourceforge.groboutils.pmti.v1.IProblemManager;
import net.sourceforge.groboutils.pmti.v1.IProblemManagerInfo;
import net.sourceforge.groboutils.pmti.v1.ProblemManagerException;

import net.sourceforge.groboutils.pmti.v1.defimpl.AbstractIssue;
import net.sourceforge.groboutils.pmti.v1.defimpl.DefaultAttribute;
import net.sourceforge.groboutils.pmti.v1.defimpl.DefaultAttributeSet;
import net.sourceforge.groboutils.pmti.v1.defimpl.DefaultAttributeInfo;
import net.sourceforge.groboutils.pmti.v1.defimpl.DefaultIssueState;
import net.sourceforge.groboutils.pmti.v1.defimpl.DefaultIssueTypeInfo;
import net.sourceforge.groboutils.pmti.v1.defimpl.DefaultListAttribute;
import net.sourceforge.groboutils.pmti.v1.defimpl.DefaultProblemManagerInfo;

import net.sourceforge.groboutils.pmti.v1.itf.parser.IParserGenerator;
import net.sourceforge.groboutils.pmti.v1.itf.parser.IParserCollator;
import net.sourceforge.groboutils.pmti.v1.itf.parser.IParser;

import java.util.Hashtable;
import java.util.Vector;
import java.util.Enumeration;


/**
 * A read-only ProblemManager for the ITF section of the PMTI framework.  No
 * states are supported.
 *
 * @author     Matt Albrecht <a href="mailto:groboclown@users.sourceforge.net">groboclown@users.sourceforge.net</a>
 * @version    $Date: 2003/02/10 22:51:59 $
 * @since      July 12, 2002
 */
public class ITFReadProblemManager implements IProblemManager
{
    
    private Hashtable issueIdToIssue = new Hashtable();
    private IIssueTypeInfo typeInfo;
    private IProblemManagerInfo pmInfo;
    private IAttributeInfo testAttribInfo;
    
    
    public ITFReadProblemManager( IParserGenerator generator )
    {
        this( generator.createParsers() );
    }
    
    
    public ITFReadProblemManager( IParser parsers )
    {
        this( new IParser[] { parsers } );
    }
    
    
    public ITFReadProblemManager( IParser[] parsers )
    {
        if (parsers == null)
        {
            throw new IllegalArgumentException("no null arguments");
        }
        
        for (int i = 0; i < parsers.length; ++i)
        {
            loadParser( parsers[i] );
        }
        
        setupInfo();
    }
    
    
    
    /**
     * Returns a list of all issue IDs known by the PMT.  This may be an
     * extremely expensive operation, depending on the size of the underlying
     * system.  This call must never return <tt>null</tt>.
     */
    public String[] getIssueIDs()
    {
        // *Sigh* JDK 1.1 again...
        String ss[];
        synchronized (this.issueIdToIssue)
        {
            ss = new String[ this.issueIdToIssue.size() ];
            Enumeration enum = this.issueIdToIssue.keys();
            for (int i = 0; enum.hasMoreElements(); ++i)
            {
                String s = (String)enum.nextElement();
                ss[i] = s;
            }
        }
        return ss;
    }
    
    
    /**
     * Returns all issue IDs that match the given 'template'.  A template is
     * an issue where all <tt>null</tt> values are considered 'wildcards',
     * so that a <tt>null</tt> state, for instance, would match for any
     * state object.  May change the template in the future to a different
     * but similar type to allow for regular-expressions.
     */
    public String[] getIssueIDsForTemplate( IIssue issue )
    {
        if (issue == null)
        {
            return new String[0];
        }
        
        if (issue.getType() != null)
        {
            // optimization:
            // there is only one type supported now, so if that type is
            // not the template type, then there is no match, otherwise
            // there is no change to the candidate list.
            if (!issue.getType().equals( typeInfo.getName() ))
            {
                return new String[0];
            }
        }
        
        if (issue.getState() != null)
        {
            // optimization:
            // there is only one state supported now, which is null.
            // So since the template expects a specific state which is
            // not null, there can be no possible match.
            return new String[0];
        }
        
        Enumeration currentCandidates;
        if (issue.getID() != null)
        {
            Vector v = new Vector();
            v.addElement( getIssueByID( issue.getID() ) );
            currentCandidates = v.elements();
        }
        else
        {
            currentCandidates = issueIdToIssue.elements();
        }
        
        if (issue.getShortDescription() != null)
        {
            String desc = issue.getShortDescription();
            Vector matches = new Vector();
            while (currentCandidates.hasMoreElements())
            {
                IIssue check = (IIssue)currentCandidates.nextElement();
                if (desc.equals( check.getShortDescription() ))
                {
                    matches.addElement( check );
                }
            }
            
            // update our valid match list
            currentCandidates = matches.elements();
        }
        
        if (issue.getAttributes() != null)
        {
            // *sigh* there's the major work...
            IAttributeSet set = issue.getAttributes();
            Vector matches = new Vector();
            while (currentCandidates.hasMoreElements())
            {
                IIssue check = (IIssue)currentCandidates.nextElement();
                if (attributesMatch( set, check.getAttributes() ))
                {
                    matches.addElement( check );
                }
            }
            currentCandidates = matches.elements();
        }
        
        Vector v = new Vector();
        while (currentCandidates.hasMoreElements())
        {
            v.addElement(
                ((IIssue)currentCandidates.nextElement()).getID() );
        }
        String s[] = new String[ v.size() ];
        v.copyInto( s );
        return s;
    }
    
    
    /**
     * Returns the issue associated with the given unique issue ID.  If no such
     * issue exists, then <tt>null</tt> is returned.  Note that the returned
     * element is a non-editable version of the issue.
     */
    public IIssue getIssueByID( String id )
    {
        return (IIssue)this.issueIdToIssue.get( id );
    }
    
    
    /**
     * Given the real issue, returns the editable version of the issue.
     */
    public IEditableIssue editIssue( IIssue issue )
            throws ProblemManagerException
    {
        throw new ProblemManagerException( "edit not allowed" );
    }
    
    
    /**
     * Creates a new issue of the given type.  If <tt>type</tt> is
     * <tt>null</tt>, then a new issue of the default issue type is
     * created and returned.
     */
    public IEditableIssue createIssue( String type )
            throws ProblemManagerException
    {
        throw new ProblemManagerException( "create not allowed" );
    }
    
    
    /**
     * Returns all meta-data for this problem management tracker.
     */
    public IProblemManagerInfo getProblemManagerInfo()
    {
        return this.pmInfo;
    }
    
    
    //-------------------------------------------------------------------------
    
    
    protected void loadParser( IParser p )
    {
        ITestIssueRecord[] tir = p.parse();
        if (tir != null)
        {
            for (int i = 0; i < tir.length; ++i)
            {
                addTestIssueRecord( tir[i] );
            }
        }
    }
    
    
    protected void addTestIssueRecord( ITestIssueRecord tir )
    {
        if (tir != null)
        {
            String id = tir.getIssueRecord().getID();
            IIssue issue = (IIssue)this.issueIdToIssue.get( id );
            // add the test record to the issue - null issues handled correctly
            issue = updateITFIssue( issue, tir );
            this.issueIdToIssue.put( id, issue );
        }
    }
    
    
    
    protected IIssue createNewITFIssue( ITestIssueRecord tir )
    {
        String id = tir.getIssueRecord().getID();
        String desc = tir.getIssueRecord().getDescription();


        return createITFIssue( id, desc, new ITestRecord[] {
                tir.getTestRecord(),
            } );
    }
    
    
    protected IIssue updateITFIssue( IIssue orig, ITestIssueRecord addTir )
    {
        if (orig == null)
        {
            return createNewITFIssue( addTir );
        }
        if (!orig.getID().equals( addTir.getIssueRecord().getID() ))
        {
            throw new IllegalArgumentException(
                "original issue id ("+orig.getID()+
                ") not same as input ITestIssueRecord ("+
                addTir.getIssueRecord().getID()+")" );
        }
        IAttribute attrib = orig.getAttributes().getAttribute(
            this.testAttribInfo.getName() );
        if (attrib == null)
        {
            throw new IllegalArgumentException(
                "invalid issue: does not contain an attribute with name "+
                this.testAttribInfo.getName() );
        }
        ITestRecord[] tr;
        if (attrib instanceof IListAttribute)
        {
            Vector v = new Vector();
            Enumeration enum = ((IListAttribute)attrib).getValues();
            while (enum.hasMoreElements())
            {
                v.addElement( (ITestRecord)enum.nextElement() );
            }
            v.addElement( addTir.getTestRecord() );
            tr = new ITestRecord[ v.size() ];
            v.copyInto( tr );
        }
        else
        {
            tr = new ITestRecord[] {
                (ITestRecord)attrib.getValue(),
                addTir.getTestRecord()
            };
        }
        
        
        String id = orig.getID();
        String desc = orig.getShortDescription();
        if (desc == null)
        {
            desc = addTir.getIssueRecord().getDescription();
        }
        
        return createITFIssue( id, desc, tr );
    }
    
    
    protected IIssue createITFIssue( String id, String desc, ITestRecord[] tr )
    {
        IAttribute a1 = new DefaultListAttribute(
            tr,
            this.testAttribInfo );
        
        IAttributeSet as = new DefaultAttributeSet( new IAttribute[] {
                a1,
            } );
        
        IIssue issue = new ITFIssue(
            id,
            this.typeInfo.getName(),
            desc,
            null,
            as );
        return issue;
    }
    
    
    protected void setupInfo()
    {
        if (typeInfo != null || pmInfo != null
            || testAttribInfo != null)
        {
            throw new IllegalStateException("already setup info.");
        }
        
        String defaultIssueName = "test issue";
        
        this.testAttribInfo = new DefaultAttributeInfo(
            "test", "test that covers this issue",
            new Class[] { ITestRecord.class } );
        
        this.typeInfo = new DefaultIssueTypeInfo( 
            defaultIssueName,
            new IAttributeInfo[] { this.testAttribInfo },
            null, null, null );
        
        this.pmInfo = new DefaultProblemManagerInfo(
            defaultIssueName,
            new IIssueTypeInfo[] { this.typeInfo } );
    }
    
    
    protected boolean attributesMatch( IAttributeSet check,
            IAttributeSet verify )
    {
        IAttribute[] checkAA = check.getAttributes();
        IAttribute[] verifyAA = verify.getAttributes();
        
        // Note: for a match to be valid, all of the check attributes
        // must exist correctly in the verify attributes, but if there are
        // verify attributes which are not in check, then it's still a
        // match.
        for (int checkIndex = 0; checkIndex < checkAA.length; ++checkIndex)
        {
            IAttribute checkA = checkAA[ checkIndex ];
            if (checkA == null)
            {
                // ignore this one - assume it's a match
                continue;
            }
            boolean notfound = true;
            
            for (int verifyIndex = 0; verifyIndex < verifyAA.length;
                ++verifyIndex)
            {
                IAttribute verifyA = verifyAA[ verifyIndex ];
                if (verifyA == null)
                {
                    // this attribute has already been matched, so skip to
                    // the next one.
                    continue;
                }
                
                if (attributeMatch( checkA, verifyA ))
                {
                    // At this point, we have verified that both attributes do
                    // indeed match each other.  Mark it as so.
                    notfound = false;
                    verifyAA[ verifyIndex ] = null;
                    break;
                }
            }
            if (notfound)
            {
                // did not find a match for the check attribute in any
                // of the verify attributes.
                return false;
            }
        }
        
        return true;
    }
    
    
    protected boolean attributeMatch( IAttribute checkA, IAttribute verifyA )
    {
        // If checkA specifies an Info, then the infos must
        // match, otherwise, it will match any attribute.
        if (checkA.getInfo() != null)
        {
            if (!verifyA.getInfo().equals( checkA.getInfo() ))
            {
                // cannot possibly be a match
                return false;
            }
        }
        
        // If checkA specifies a value, then both values must match,
        // otherwise it will match any value.
        if (checkA.getValue() != null)
        {
            // if they're both list attributes, then check need to only
            // contain a sub-set of the verify elements.
            if (checkA instanceof IListAttribute)
            {
                if (!(verifyA instanceof IListAttribute))
                {
                    // cannot possibly be a match
                    return false;
                }
                IListAttribute verifyLA = (IListAttribute)verifyA;
                Enumeration checkE = ((IListAttribute)checkA).getValues();
                while (checkE.hasMoreElements())
                {
                    Object checkO = checkE.nextElement();
                    Enumeration verifyE = verifyLA.getValues();
                    boolean enumNotfound = true;
                    while (verifyE.hasMoreElements())
                    {
                        if (checkO.equals( verifyE.nextElement() ))
                        {
                            // matched this value.
                            enumNotfound = false;
                            break;
                        }
                    }
                    if (enumNotfound)
                    {
                        // no match for the check's value.
                        return false;
                    }
                }
            }
            else
            {
                if (!checkA.getValue().equals( verifyA.getValue() ))
                {
                    // values don't match.
                    return false;
                }
            }
        }
        
        
        // We have verified that both attributes do indeed match each other.
        return true;
    }
    
    
    
    private static class ITFIssue extends AbstractIssue
    {
        public ITFIssue( String i, String t, String d, IIssueState s,
                IAttributeSet a )
        {
            super( i, t, d, s, a );
        }
        
        
        public IIssue reload()
            throws ProblemManagerException
        {
            // assumption is that this problem manager is read-only, so
            // it won't change underneath us.
            
            // Yes, that's not necessarily true, but it keeps the
            // implementation simple.
            
            return this;
        }
    }
}

