/*
 * @(#)ClassMarkSet.java
 *
 * Copyright (C) 2002,2003 Matt Albrecht
 * groboclown@users.sourceforge.net
 * 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.codecoverage.v2.report;


import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import net.sourceforge.groboutils.codecoverage.v2.IAnalysisMetaData;
import net.sourceforge.groboutils.codecoverage.v2.IChannelLogRecord;
import net.sourceforge.groboutils.codecoverage.v2.datastore.MarkRecord;

/**
 * Contains all the marks (sorted by covered and not covered) per method.
 *
 * @author    Matt Albrecht <a href="mailto:groboclown@users.sourceforge.net">groboclown@users.sourceforge.net</a>
 * @version   $Date: 2004/04/15 05:48:26 $
 * @since     December 17, 2002
 * @see       IAnalysisMetaData
 */
public class ClassMarkSet
{
    private static final org.apache.log4j.Logger LOG =
        org.apache.log4j.Logger.getLogger( ClassMarkSet.class );
    
    private String[] methodNames;
    private Map methodToCoveredMarks;
    private Map methodToNotCoveredMarks;
    
    
    //------------------------------------------------------------------------
    
    /**
     * Container data for a mark and whether it were covered.
     * Instances of this class are only used in the ClassMarkSet constructor.
     */
    private static class DidCover
    {
        public MarkRecord mr;
        public boolean wasCovered;
        
        public DidCover( MarkRecord mr )
        {
            if (mr == null)
            {
                throw new IllegalArgumentException("no null args.");
            }
            this.mr = mr;
        }
        
        
        public void cover()
        {
            this.wasCovered = true;
        }
        
        public String toString()
        {
            return this.mr.toString();
        }
    }
    
    
    //------------------------------------------------------------------------
    
    /**
     * Convienient way to store marks and their mark index relationship.
     * Instances of this class are only used in the ClassMarkSet constructor.
     */
    private static class MarkSet
    {
        private short methodIndex;
        private String methodName;
        private Map markSet = new HashMap();
        
        public MarkSet( String name, short methIndex )
        {
            this.methodName = name;
            this.methodIndex = methIndex;
        }
        
        
        public String getMethodName()
        {
            return this.methodName;
        }
        
        
        public short getMethodIndex()
        {
            return this.methodIndex;
        }
        
        
        public void addMark( MarkRecord mr )
        {
            if (this.methodIndex != mr.getMethodIndex())
            {
                throw new IllegalStateException(
                    "Put mark in wrong method bucket.");
            }
            Short index = new Short( mr.getMarkIndex() );
            if (!this.markSet.containsKey( index ))
            {
                this.markSet.put( index, new DidCover( mr ) );
            }
            else
            {
                throw new IllegalArgumentException( "Mark index "+index+
                    " is in the MarkRecord list mroe than once." );
            }
        }
        
        
        public void coverMark( short index )
        {
            DidCover dc = getMark( index );
            if (dc != null)
            {
                dc.cover();
            }
            else
            {
                // the marks and the logs are out-of-sync
                /*
                LOG.fatal( "Channel log record "+
                    "has mark "+index+" in method "+getMethodName()+
                    " [index "+getMethodIndex()+
                    "] which is not known by the class records." );
                */
                throw new IllegalArgumentException( "Channel log record "+
                    "has mark "+index+" in method "+getMethodName()+
                    " [index "+getMethodIndex()+
                    "] which is not known by the class records." );
            }
        }
        
        
        public DidCover getMark( short index )
        {
            return (DidCover)this.markSet.get( new Short( index ) );
        }
        
        
        public MarkRecord[] getMarksOfType( boolean type )
        {
            List out = new ArrayList();
            Iterator iter = this.markSet.values().iterator();
            while (iter.hasNext())
            {
                DidCover dc = (DidCover)iter.next();
                if (dc.wasCovered == type)
                {
                    out.add( dc.mr );
                }
            }
            return (MarkRecord[])out.toArray( new MarkRecord[ out.size() ] );
        }
    }
    
    
    //------------------------------------------------------------------------
    
    /**
     * Convienient way to store the sorted methods and their data.
     * Instances of this class are only used in the ClassMarkSet constructor.
     */
    private static class MethodSet
    {
        private String methodNames[];
        private Map perMethodItems = new HashMap();
        
        public MethodSet( String methNames[], MarkRecord mrL[] )
        {
            this.methodNames = copyStringArray( methNames );
            for (int i = 0; i < methNames.length; ++i)
            {
                if (methNames[i] == null)
                {
                    throw new IllegalArgumentException( "No null args." );
                }
                this.perMethodItems.put( methNames[i],
                    new MarkSet( methNames[i], (short)i ) );
            }
            
            int len = mrL.length;
            for (int i = 0; i < len; ++i)
            {
                if (mrL[i] == null)
                {
                    throw new IllegalArgumentException( "No null args." );
                }
                String sig = mrL[i].getMethodSignature();
                MarkSet ms = get( sig );
                if (ms == null)
                {
                    throw new IllegalArgumentException(
                        "Found mark for method "+sig+
                        " which was not in the method list." );
                }
                
                // assurance
                if (ms.getMethodIndex() != mrL[i].getMethodIndex())
                {
                    throw new IllegalArgumentException(
                        "The signature order from ClassRecord ["+
                        ms.getMethodIndex()+
                        " does not match the method index ["+
                        mrL[i].getMethodIndex()+"] for the mark." );
                }
                
                ms.addMark( mrL[i] );
            }
        }
        
        public int getMethodCount()
        {
            return this.methodNames.length;
        }
        
        public String[] getMethodNames()
        {
            return copyStringArray( this.methodNames );
        }
        
        public short getMethodIndex( String methName )
        {
            MarkSet ms = get( methName );
            if (ms == null)
            {
                return (short)-1;
            }
            return ms.getMethodIndex();
        }
        
        public String getMethodByIndex( short index )
        {
            int iindex = (int)index;
            String names[] = getMethodNames();
            if (iindex < 0 || iindex >= names.length)
            {
                return null;
            }
            return names[ iindex ];
        }
        
        
        public Iterator nameIterator()
        {
            return this.perMethodItems.keySet().iterator();
        }
        
        
        public MarkSet get( String methName )
        {
            return (MarkSet)this.perMethodItems.get( methName );
        }
        
        
        public MarkSet get( short methodIndex )
        {
            return get( getMethodByIndex( methodIndex ) );
        }
    }
    
    
    //------------------------------------------------------------------------
    
    
    /**
     * 
     */
    ClassMarkSet( String className, String methodSigs[],
            MarkRecord[] marks, IChannelLogRecord[] classLogs )
    {
        if (marks == null || classLogs == null || methodSigs == null)
        {
            throw new IllegalArgumentException( "No null args." );
        }
        
        this.methodNames = copyStringArray( methodSigs );
        MethodSet set = new MethodSet( methodSigs, marks );
        coverMarks( set, classLogs );
        this.methodToCoveredMarks = getMarksOfType( set, true );
        this.methodToNotCoveredMarks = getMarksOfType( set, false );
    }
    
    
    /**
     * Return all the methods known by the list of marks and class logs.
     */
    public String[] getMethodSignatures()
    {
        String s[] = this.methodNames;
        List list = new ArrayList( s.length );
        for (int i = 0; i < s.length; ++i)
        {
            if (s[i] != null)
            {
                list.add( s[i] );
            }
        }
        return (String[])list.toArray( new String[ list.size() ] );
    }
    
    
    /**
     * Return all the marks that were covered during the execution of the
     * given method.  Guaranteed to never return null.
     */
    public MarkRecord[] getCoveredMarksForMethod( String methodSig )
    {
        MarkRecord[] mrL = (MarkRecord[])this.methodToCoveredMarks.get(
            methodSig );
        if (mrL == null)
        {
            throw new IllegalArgumentException( "Unknown method: "+methodSig );
        }
        return mrL;
    }
    
    
    /**
     * Return all the marks that were not executed during the runtime
     * of the given method.  Guaranteed to never return null.
     */
    public MarkRecord[] getNotCoveredMarksForMethod( String methodSig )
    {
        MarkRecord[] mrL = (MarkRecord[])this.methodToNotCoveredMarks.get(
            methodSig );
        if (mrL == null)
        {
            throw new IllegalArgumentException( "Unknown method: "+methodSig );
        }
        return mrL;
    }
    
    
    
    //------------------------------------------------------------------------
    // Examine the input data and organize it
    
    
    private void coverMarks( MethodSet methods,
            IChannelLogRecord[] classLogs )
    {
        for (int i = 0; i < classLogs.length; ++i)
        {
            IChannelLogRecord clr = classLogs[i];
            if (clr == null)
            {
                throw new IllegalArgumentException( "no null args" );
            }
            short markIndex = clr.getMarkIndex();
            short methIndex = clr.getMethodIndex();
            //LOG.debug( "Covering method "+mi+", mark "+markIndex );
            
            MarkSet ms = methods.get( methIndex );
            if (ms != null)
            {
                ms.coverMark( markIndex );
            }
            else
            {
                // the marks and the logs are out-of-sync
                /*
                // let's assume that it's ok.
                LOG.fatal( "Channel log record "+
                    "refers to a method index ("+methIndex+
                    ") that is not known by the class records (mark = "+
                    markIndex+")." );
                */
                throw new IllegalArgumentException( "Channel log record "+
                    "refers to a method index ("+methIndex+
                    ") that is not known by the class records (mark = "+
                    markIndex+")." );
            }
        }
    }
    
    
    private Map getMarksOfType( MethodSet methods,
            boolean covered )
    {
        Map map = new HashMap();
        Iterator iter = methods.nameIterator();
        while (iter.hasNext())
        {
            String methName = (String)iter.next();
            MarkSet ms = methods.get( methName );
            map.put( methName, ms.getMarksOfType( covered ) );
        }
        return map;
    }
    
    
    private static String[] copyStringArray( String in[] )
    {
        int len = in.length;
        String out[] = new String[ len ];
        System.arraycopy( in, 0, out, 0, len );
        return out;
    }
}

