//______________________________________________________________________________
//   |       |
//   | knipp |           Knipp Medien und Kommunikation GmbH
//    -------
//
//
//     Knipp RRI Toolkit
//
//
//     Copyright (C) 2005 by Knipp Medien und Kommunikation GmbH
//
//     This library is free software; you can redistribute it and/or
//     modify it under the terms of the GNU Lesser General Public License 
//     as published by the Free Software Foundation; either version 2.1 
//     of the License, or any later version.
// 
//     This library is distributed in the hope that it will be useful, 
//     but WITHOUT ANY WARRANTY; without even the implied warranty of
//     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 
//     GNU Lesser General Public License for more details.
//
//     You should have received a copy of the GNU Lesser General
//     Public License along with this library; if not, write to the 
//     Free Software Foundation, Inc., 59 Temple Place, Suite 330, 
//     Boston, MA 02111-1307 USA 
//
//
//     initial author:    Klaus Malorny
//
//     version:           1.4 (15127), 2011/06/03 14:52:49
//                        $Revision: 14936 $ 
//                        $Date: 2010-11-05 15:57:40 +0100 (Fri, 05 Nov 2010) $
//                        $Author: Klaus.Malorny $
//

package de.knipp.rritoolkit.response;

import java.util.Collection;
import java.util.Map;
import java.util.HashMap;
import java.util.List;
import java.util.ArrayList;
import java.util.IdentityHashMap;

/**
 *  helper class that maps the DENIC error codes to objects (typically
 *  enum constants)
 *
 *  @param <E>             the type error codes are mapped to
 *
 *  @version  $Revision: 14936 $
 *  @author   Klaus Malorny
 */

public class ErrorMapper<E>
{
  /** the mapping from the pattern to the user object */
  
  private Map<ErrorPattern, E> patternMap;
  
  
  /** list of pattern that need to be tested in any case */
  
  private List<ErrorPattern> genericPatterns;
  
  
  /** the patterns to investigate for a certain code */
  
  private Map<String, List<ErrorPattern>> codePatterns;
  
  
  /** the patterns to investigate for a certain error class code */
  
  private Map<String, List<ErrorPattern>> classPatterns;
  
  
  /** the patterns to investigate for a cetain access code */
  
  private Map<String, List<ErrorPattern>> accessPatterns;
  
  
  /** the patterns to investigate for a certain application code */
  
  private Map<String, List<ErrorPattern>> applicationPatterns;
  
  
  /** the patterns to investigate for a certain component code */
  
  private Map<String, List<ErrorPattern>> componentPatterns;
  
  
  /** the patterns to investiage for a certain error code */
  
  private Map<String, List<ErrorPattern>> errorPatterns;
  

  /** our visitor for adding new patterns */
  
  private Visitor visitor;
  
  
  
  /**
   *  constructor
   */
  
  public ErrorMapper ()
  {
    patternMap = new IdentityHashMap<ErrorPattern, E> ();
    genericPatterns = new ArrayList<ErrorPattern> ();
    codePatterns = new HashMap<String, List<ErrorPattern>> ();
    classPatterns = new HashMap<String, List<ErrorPattern>> ();
    accessPatterns = new HashMap<String, List<ErrorPattern>> ();
    applicationPatterns = new HashMap<String, List<ErrorPattern>> ();
    componentPatterns = new HashMap<String, List<ErrorPattern>> ();
    errorPatterns = new HashMap<String, List<ErrorPattern>> ();
    visitor = new Visitor ();
  }
  

  
  /**
   *  add a new pattern to the mapper. A pattern may not be used for multiple
   *  user objects
   *
   *  @param pattern                  the pattern
   *  @param obj                      the user object
   */
  
  public void add (ErrorPattern pattern, E obj)
  {
    patternMap.put (pattern, obj);
    pattern.accept (visitor);
  }
 
  
  
  /**
   *  test all patterns in the given list for a match. For those patterns
   *  which match the corresponding element is added
   *
   *  @param list                  the list, may be {@code null}
   *  @param ec                    the error code to match
   *  @param result                the collection, to which the found elements
   *                               are added
   *  @return                      {@code true} if at least one match
   *                               has been found
   */
  
  private boolean testList (List<ErrorPattern> list, ErrorCode ec,
    Collection<E> result)
  {
    if (list == null) return false;
    
    boolean anyFound = false;
    
    for (ErrorPattern pattern: list)
    {
      if (pattern.matches (ec))
      {
        result.add (patternMap.get (pattern));
        anyFound = true;
      }
    }
    
    return anyFound;
  }
  
  
  
  /**
   *  test the given error code and add all objects whose related pattern
   *  matches to the given collection
   *
   *  @param ec                    the error code
   *  @param result                the collection, to which the found elements
   *                               are added
   *  @return                      {@code true} if at least one match
   *                               has been found
   */
  
  public boolean findMatches (ErrorCode ec, Collection<E> result)
  {
    // don't use || instead of | -- we want all calls to be performed!
    
    boolean b = testList (genericPatterns, ec, result);
    b |= testList (codePatterns.get (ec.getCode ()), ec, result);
    b |= testList (classPatterns.get (ec.getErrorClass ()), ec, result);
    b |= testList (accessPatterns.get (ec.getInterfaceAccess ()), ec, result);
    
    b |= testList (applicationPatterns.get (
      ec.getInterfaceApplication ()), ec, result);
    
    b |= testList (componentPatterns.get (ec.getComponent ()), ec, result);
    b |= testList (errorPatterns.get (ec.getErrorCode ()), ec, result);
    
    return b;
  }
  
  
  /**
   *  test the given result message and add all objects whose related pattern
   *  matches to the given collection
   *
   *  @param msg                   the result message
   *  @param result                the collection, to which the found elements
   *                               are added
   *  @return                      {@code true} if at least one match
   *                               has been found
   */
  
  public boolean findMatches (ResultMessage msg, Collection<E> result)
  {
    return findMatches (msg.getCode (), result);
  }
  
  
  
  /**
   *  test the given result messages and add all objects whose related pattern
   *  matches to the given list of messages
   *
   *  @param msgs                  the result messages
   *  @param result                the collection, to which the found elements
   *                               are added
   */
  
  public void findMatches (Iterable<ResultMessage> msgs, Collection<E> result)
  {
    for (ResultMessage msg: msgs) findMatches (msg, result);
  }
  
  
  
  
  
  /**
   *  test the given response and add all objects whose related pattern
   *  matches any result message of the resposne
   *
   *  @param resp                  the resposne
   *  @param result                the collection, to which the found elements
   *                               are added
   */
   
  public void findMatches (Response resp, Collection<E> result)
  {
    findMatches (resp.getResultMessages (), result);
  }
  
  
  
  
  
  /**
   *  filter the given result messages that match one of the given patterns
   *
   *  @param msgs                  the messages
   *  @param patterns              the patterns
   *  @return                      a list of matching messages
   */
  
  public static List<ResultMessage> filter (Iterable<ResultMessage> msgs, 
    ErrorPattern... patterns)
  {
    List<ResultMessage> result = new ArrayList<ResultMessage> ();
    
    for (ResultMessage msg: msgs)
    {
      ErrorCode ec = msg.getCode ();
      
      for (ErrorPattern pattern: patterns)
      {
        if (pattern.matches (ec))
        {
          result.add (msg);
          break;
        }
      }
    }
    
    return result;
  }
  
  
  
  
  /**
   *  small inner class that implements the visitor interface for the patterns
   *
   *  @version  $Revision: 14936 $
   *  @author   Klaus Malorny
   */
  
  private class Visitor implements ErrorPatternVisitor
  {
    
    /**
     *  add a pattern to a list
     *
     *  @param map                      the map
     *  @param code                     the code that the pattern matches
     *  @param pattern                  the pattern
     */
    
    private void add (Map<String, List<ErrorPattern>> map, String code, 
      ErrorPattern pattern)
    {
      List<ErrorPattern> list = map.get (code);
      
      if (list == null)
      {
        list = new ArrayList<ErrorPattern> ();
        map.put (code, list);
      }
      
      list.add (pattern);
    }
    
    
    /**
     *  called by an opaque implementation of ErrorPattern
     *
     *  @param pattern                  the pattern
     */

    @Override
    public void visit (ErrorPattern pattern)
    {
      genericPatterns.add (pattern);
    }



    /**
     *  called by a simple pattern
     *
     *  @param pattern                  the pattern
     */

    @Override
    public void visit (SimplePattern pattern)
    {
      add (codePatterns, pattern.getCode (), pattern);
    }

    
    
    

    /**
     *  called by a group pattern
     *
     *  @param pattern                  the pattern
     */

    @Override
    public void visit (GroupPattern pattern)
    {
      // the pattern is added to one list only. The code components are
      // tested with a decreasing order of their length, i.e. error code,
      // application, component, class, access.
      
      String[] temp = pattern.getErrorCodes ();
      
      if (temp != null)
      {
        for (String code: temp) add (errorPatterns, code, pattern);
        return;
      }
      
      temp = pattern.getApplicationCodes ();
      
      if (temp != null)
      {
        for (String code: temp) add (applicationPatterns, code, pattern);
        return;
      }
      
      temp = pattern.getComponentCodes ();
      
      if (temp != null)
      {
        for (String code: temp) add (componentPatterns, code, pattern);
        return;
      }
      
      temp = pattern.getClassCodes ();
      
      if (temp != null)
      {
        for (String code: temp) add (classPatterns, code, pattern);
        return;
      }
      
      temp = pattern.getAccessCodes ();
      
      if (temp != null)
      {
        for (String code: temp) add (accessPatterns, code, pattern);
        return;
      }
      
      // should not be reached. If it actually happens, put it into
      // the generic list
      
      genericPatterns.add (pattern);
    }
      
    
  }
  
  
}


