/**
 * Copyright (c) 2004-2010 IBM Corporation and others.
 * All rights reserved.   This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v2.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v20.html
 *
 * Contributors:
 *   IBM - Initial API and implementation
 */
package org.eclipse.emf.common.util;

import java.util.Collections;
import java.util.List;

/**
 * A basic implementation of a diagnostic that that also acts as a chain.
 */
public class BasicDiagnostic implements Diagnostic, DiagnosticChain
{
  /**
   * The severity.
   * @see #getSeverity
   */
  protected int severity;

  /**
   * The message.
   * @see #getMessage
   */
  protected String message;

  /**
   * The message.
   * @see #getMessage
   */
  protected List<Diagnostic> children;

  /**
   * The data.
   * @see #getData
   */
  protected List<?> data;

  /**
   * The source.
   * @see #getSource
   */
  protected String source;

  /**
   * The code.
   * @see #getCode
   */
  protected int code;
  
  /**
   * Default Constructor (no initialization for local parameters)
   */
  public BasicDiagnostic()
  {
    super();
  }

  public BasicDiagnostic(String source, int code, String message, Object[] data) 
  {
    this.source = source;
    this.code = code;
    this.message = message;
    this.data = dataAsList(data);
  }

  public BasicDiagnostic(int severity, String source, int code, String message, Object[] data)
  {
    this(source, code, message, data);
    this.severity = severity;
  }

  public BasicDiagnostic(String source, int code, List<? extends Diagnostic> children, String message, Object[] data)
  {
    this(source, code, message, data);
    if (children != null)
    {
      for (Diagnostic diagnostic : children)
      {
        add(diagnostic);
      }
    }
  }

  protected List<?> dataAsList(Object [] data)
  {
    if (data == null)
    {
      return Collections.EMPTY_LIST;
    }
    else
    {
      Object [] copy = new Object [data.length];
      System.arraycopy(data, 0, copy, 0, data.length);
      return new BasicEList.UnmodifiableEList<Object>(copy.length, copy);
    }
  }
  
  protected void setSeverity(int severity)
  {
    this.severity = severity;
  }

  public int getSeverity()
  {
    return severity;
  }

  public String getMessage()
  {
    return message;
  }

  public List<?> getData()
  {
    return data;
  }

  public List<Diagnostic> getChildren()
  {
    if (children == null)
    {
      return Collections.emptyList();
    }
    else
    {
      return Collections.unmodifiableList(children);
    }
  }

  protected void setSource(String source)
  {
    this.source = source;
  }

  public String getSource()
  {
    return source;
  }
  
  protected void setCode(int code)
  {
    this.code = code;
  }

  public int getCode()
  {
    return code;
  }

  public void add(Diagnostic diagnostic)
  {
    if (children == null)
    {
      children = new BasicEList<Diagnostic>();
    }

    children.add(diagnostic);
    int childSeverity = diagnostic.getSeverity();
    if (childSeverity > getSeverity())
    {
      severity = childSeverity;
    }
  }

  public void addAll(Diagnostic diagnostic)
  {
    for (Diagnostic child : diagnostic.getChildren())
    {
      add(child);
    }
  }

  public void merge(Diagnostic diagnostic)
  {
    if (diagnostic.getChildren().isEmpty())
    {
      add(diagnostic);
    }
    else
    {
      addAll(diagnostic);
    }
  }

  public int recomputeSeverity()
  {
    if (children != null)
    {
      severity = OK;
      for (Diagnostic child : children)
      {
        int childSeverity = child instanceof BasicDiagnostic ? ((BasicDiagnostic)child).recomputeSeverity() : child.getSeverity();
        if (childSeverity > severity)
        {
          severity = childSeverity;
        }
      }
    }

    return severity;
  }
  
  /**
   * Returns the first throwable object available in the {@link #data} list, 
   * which is set when this diagnostic is instantiated.
   */
  public Throwable getException()
  {
    List<?> data = getData();
    if (data != null)
    {
      for (Object datum : data)
      {
        if (datum instanceof Throwable)
        {
          return (Throwable)datum;
        }
      }
    }
    return null;
  }
  
  @Override
  public String toString()
  {
    StringBuilder result = new StringBuilder();
    result.append("Diagnostic ");
    switch (severity)
    {
      case OK: 
      {
        result.append("OK"); 
        break;
      }
      case INFO: 
      {
        result.append("INFO"); 
        break;
      }
      case WARNING: 
      {
        result.append("WARNING"); 
        break;
      }
      case ERROR: 
      {
        result.append("ERROR"); 
        break;
      }
      case CANCEL: 
      {
        result.append("CANCEL"); 
        break;
      }
      default:
      {
        result.append(Integer.toHexString(severity));
        break;
      }
    }

    result.append(" source="); 
    result.append(source);

    result.append(" code="); 
    result.append(code);

    result.append(' ');
    result.append(message);

    if (data != null)
    {
      result.append(" data=");
      result.append(data);
    }
    if (children != null)
    {
      result.append(' ');
      result.append(children);
    }

    return result.toString();
  }  


  /**
   * Returns the throwable viewed as a {@link Diagnostic}.
   * 
   * @param throwable
   * @return {@link Diagnostic}
   */
  public static Diagnostic toDiagnostic(Throwable throwable)
  {
    if (throwable instanceof DiagnosticException)
    {
      return ((DiagnosticException)throwable).getDiagnostic();
    }
    else if (throwable instanceof WrappedException)
    {
      return toDiagnostic(throwable.getCause());
    }
    
    String message = throwable.getClass().getName();
    int index = message.lastIndexOf('.');
    if (index >= 0)
    {
      message = message.substring(index + 1);
    }
    if (throwable.getLocalizedMessage() != null)
    {
      message = message + ": " + throwable.getLocalizedMessage();
    }
    
    BasicDiagnostic basicDiagnostic = 
      new BasicDiagnostic
        (Diagnostic.ERROR,
         "org.eclipse.emf.common", 
         0, 
         message,
         new Object[] { throwable });
    
    if (throwable.getCause() != null && throwable.getCause() != throwable)
    {
      throwable = throwable.getCause();
      basicDiagnostic.add(toDiagnostic(throwable));
    }
    
    return basicDiagnostic;
  }
}
