/**
 * This class is responsible for updating chart value of extension chart elements.
 *
 * @since 3.7
 */
public class ChartExtensionValueUpdater {

  private ILogger logger = Logger.getLogger("org.eclipse.birt.chart.engine/trace"); // $NON-NLS-1$

  /** This set identifies which elements has visible attribute. */
  private static Set<String> hasVisibleElementSet = new HashSet<String>();

  static {
    hasVisibleElementSet.add(LineAttributes.class.getSimpleName());
    hasVisibleElementSet.add(Marker.class.getSimpleName());
    hasVisibleElementSet.add(Label.class.getSimpleName());
    hasVisibleElementSet.add(Series.class.getSimpleName());
    hasVisibleElementSet.add(Block.class.getSimpleName());
    hasVisibleElementSet.add(ClientArea.class.getSimpleName());
  }

  /**
   * Returns <code>true</code> if specified class contains 'visible' attribute.
   *
   * @param clazz
   * @return
   */
  static boolean contanisVisibleElement(EClass clazz) {
    boolean contains = hasVisibleElementSet.contains(clazz.getName());
    if (contains) {
      return true;
    }
    EList<EClass> supers = clazz.getEAllSuperTypes();
    if (supers.size() > 0) {
      for (EClass eSuper : supers) {
        contains = hasVisibleElementSet.contains(eSuper.getName());
        if (contains) {
          return true;
        }
      }
    }

    return contains;
  }

  public static boolean isMapEntry(EClass eClass) {
    return (eClass.getInstanceClass() == Map.Entry.class);
  }

  @SuppressWarnings({"unchecked", "rawtypes"})
  private void updateAttrs(
      EClass eClass, EObject eParentObj, EObject eObj, EObject eRef, EObject eDef) {

    List<EAttribute> listMany = new LinkedList<EAttribute>();
    List<EAttribute> list = new LinkedList<EAttribute>();

    for (EAttribute eAttr : eClass.getEAllAttributes()) {
      if (eAttr.isMany()) {
        listMany.add(eAttr);
      } else {
        list.add(eAttr);
      }
    }

    for (EAttribute eAttr : listMany) {
      List<?> vList = (List<?>) eObj.eGet(eAttr);
      if (vList.size() == 0) {
        if (eRef != null && ((List<?>) eRef.eGet(eAttr)).size() > 0) {
          vList.addAll((List) eRef.eGet(eAttr));
        } else if (eDef != null) {
          vList.addAll((List) eDef.eGet(eAttr));
        }
      }
    }

    for (EAttribute eAttr : list) {
      Object val = eObj.eGet(eAttr);
      if (eAttr.isUnsettable()) {
        if (!eObj.eIsSet(eAttr)) {
          if (eRef != null && eRef.eIsSet(eAttr)) {
            eObj.eSet(eAttr, eRef.eGet(eAttr));
          } else if (eDef != null && eDef.eIsSet(eAttr)) {
            eObj.eSet(eAttr, eDef.eGet(eAttr));
          }
        }
      } else if (val == null) {
        if (eRef != null && eRef.eGet(eAttr) != null) {
          eObj.eSet(eAttr, eRef.eGet(eAttr));
        } else if (eDef != null) {
          eObj.eSet(eAttr, eDef.eGet(eAttr));
        }
      }
    }
  }

  /**
   * Updates chart element object.
   *
   * @param expected class of expected chart element.
   * @param name chart element name
   * @param eParentObj container of chart element object.
   * @param eObj chart element object.
   * @param eRef reference chart object to be used to update chart object's values.
   * @param eDef default chart object to be used to update chart object's values.
   */
  public void update(
      EClass expected, String name, EObject eParentObj, EObject eObj, EObject eRef, EObject eDef) {
    if (eObj == null) {
      if (eRef != null) {
        if (eRef instanceof IChartObject) {
          eObj = ((IChartObject) eRef).copyInstance();
          ChartElementUtil.setEObjectAttribute(eParentObj, name, eObj, false);
        }
      } else if (eDef != null) {
        if (eDef instanceof IChartObject) {
          eObj = ((IChartObject) eDef).copyInstance();
          ChartElementUtil.setEObjectAttribute(eParentObj, name, eObj, false);
          return;
        }
      }
    }
    if (eObj == null || (eRef == null && eDef == null)) {
      return;
    }

    // Process visible case.
    if (contanisVisibleElement(eObj.eClass())) {
      if (eObj.eIsSet(eObj.eClass().getEStructuralFeature("visible"))) // $NON-NLS-1$
      {
        if (eObj.eGet(eObj.eClass().getEStructuralFeature("visible"))
            != Boolean.TRUE) // $NON-NLS-1$
        {
          // If the visible attribute is set to false, directly return, no need
          // to update other attributes.
          return;
        }
      } else {
        // If eObj isn't set visible and the visible attribute of
        // reference object is set to false, directly return, no need to
        // update other attributes.
        if (eRef != null
            && eRef.eIsSet(eRef.eClass().getEStructuralFeature("visible"))) // $NON-NLS-1$
        {
          if (eRef.eGet(eRef.eClass().getEStructuralFeature("visible"))
              != Boolean.TRUE) // $NON-NLS-1$
          {
            eObj.eSet(eRef.eClass().getEStructuralFeature("visible"), Boolean.FALSE); // $NON-NLS-1$
            return;
          }
        } else if (eDef != null
            && eDef.eIsSet(eDef.eClass().getEStructuralFeature("visible"))) // $NON-NLS-1$
        {
          if (eDef.eGet(eDef.eClass().getEStructuralFeature("visible"))
              != Boolean.TRUE) // $NON-NLS-1$
          {
            eObj.eSet(eDef.eClass().getEStructuralFeature("visible"), Boolean.FALSE); // $NON-NLS-1$
            return;
          }
        }
      }
    }

    EClass eClass = eObj.eClass();

    // attributes
    updateAttrs(eClass, eParentObj, eObj, eRef, eDef);

    // list attributes

    // references
    for (EReference ref : eClass.getEAllReferences()) {
      String childName = ref.getName();
      Object child = eObj.eGet(ref);
      Object refChild = eRef != null ? eRef.eGet(ref) : null;
      Object defChild = eDef != null ? eDef.eGet(ref) : null;
      EObject eChildParntObj = eObj;
      if (child == null) {
        if (refChild != null) {
          if (refChild instanceof IChartObject) {
            child = updateFromReference(childName, refChild, eChildParntObj);
          }
        } else if (defChild != null) {
          if (defChild instanceof IChartObject) {
            child = ((IChartObject) defChild).copyInstance();
            ChartElementUtil.setEObjectAttribute(eChildParntObj, childName, child, false);
            continue;
          }
        }
      }

      if (child != null) {
        if (ref.isMany()) {
          int size = ((List<?>) child).size();
          for (int i = 0; i < size; i++) {
            Object item = ((List<?>) child).get(i);
            Object refItem =
                (refChild == null || (i >= ((List<?>) refChild).size()))
                    ? null
                    : ((List<?>) refChild).get(i);
            Object defItem =
                (defChild == null || (i >= ((List<?>) defChild).size()))
                    ? null
                    : ((List<?>) defChild).get(i);
            update(ref, eObj, (EObject) item, (EObject) refItem, (EObject) defItem);
          }
        } else {
          update(ref, eObj, (EObject) child, (EObject) refChild, (EObject) defChild);
        }
      }
    }
  }

  protected Object updateFromReference(String childName, Object refChild, EObject eChildParntObj) {
    Object child = ((IChartObject) refChild).copyInstance();
    ChartElementUtil.setEObjectAttribute(eChildParntObj, childName, child, false);
    return child;
  }

  private Map<String, EObject> defaultObjCache = new HashMap<String, EObject>();

  /**
   * Returns a chart element instance with default value.
   *
   * @param expected
   * @param name
   * @param eObj
   * @return a chart element instance with default value.
   */
  public EObject getDefault(EClass expected, String name, EObject eObj) {
    EObject def = defaultObjCache.get(eObj.getClass().getSimpleName());
    if (def != null) {
      return def;
    }

    Method m;
    try {
      m = eObj.getClass().getMethod("create"); // $NON-NLS-1$
      EObject object = (EObject) m.invoke(eObj);
      defaultObjCache.put(eObj.getClass().getSimpleName(), object);
      return object;
    } catch (SecurityException e) {
      logger.log(e);
    } catch (NoSuchMethodException e) {
      logger.log(e);
    } catch (IllegalArgumentException e) {
      logger.log(e);
    } catch (IllegalAccessException e) {
      logger.log(e);
    } catch (InvocationTargetException e) {
      logger.log(e);
    }
    return null;
  }

  private void update(
      EReference ref, EObject eParentObj, EObject eObj, EObject eRef, EObject eDef) {
    if (eObj != null && eObj instanceof DataPointComponent) {
      eDef =
          ChartDefaultValueUtil.getPercentileDataPointDefObj(
              (DataPointComponent) eObj, (DataPointComponent) eDef);
    }

    update(ref.getEReferenceType(), ref.getName(), eParentObj, eObj, eRef, eDef);
  }
}
/**
 * Used as base class for Plot computation. Abstract useful methods from PlotWithAxes and
 * PlotWithoutAxes.
 */
public abstract class PlotComputation {

  protected static final ILogger logger =
      Logger.getLogger("org.eclipse.birt.chart.engine/computation"); // $NON-NLS-1$

  protected static final IGObjectFactory goFactory = GObjectFactory.instance();

  /** An internal XServer implementation capable of obtaining text metrics, etc. */
  protected final IDisplayServer ids;

  /** The runtime context associated with chart generation */
  protected final RunTimeContext rtc;

  protected final IChartComputation cComp;

  /** A final internal reference to the model used in rendering computations */
  protected final Chart cm;

  /**
   * Insets maintained as pixels equivalent of the points value specified in the model used here for
   * fast computations
   */
  protected Insets insCA = null;

  /** Ratio for converting a point to a pixel */
  protected transient double dPointToPixel = 0;

  public PlotComputation(IDisplayServer ids, RunTimeContext rtc, Chart cm) {
    this.rtc = rtc;
    this.cComp = rtc.getState(StateKey.CHART_COMPUTATION_KEY);
    this.ids = ids;
    this.cm = cm;
    dPointToPixel = ids.getDpiResolution() / 72d;
  }

  /**
   * A computed plot area based on the block dimensions and the axis attributes and label values
   * (within axes)
   */
  protected Bounds boPlotBackground = goFactory.createBounds(0, 0, 100, 100);

  /**
   * This method computes the entire chart within the given bounds. If the dataset has changed but
   * none of the axis attributes have changed, simply re-compute without 'rebuilding axes'.
   *
   * @param bo
   */
  public abstract void compute(Bounds bo) throws ChartException, IllegalArgumentException;

  /**
   * @param sdOrthogonal
   * @param seOrthogonal
   * @return ISeriesRenderingHints
   * @throws ChartException
   * @throws IllegalArgumentException
   */
  public abstract ISeriesRenderingHints getSeriesRenderingHints(
      SeriesDefinition sdOrthogonal, Series seOrthogonal)
      throws ChartException, IllegalArgumentException;

  /** @return The plot bounds in pixels */
  public final Bounds getPlotBounds() {
    return boPlotBackground;
  }

  public Chart getModel() {
    return cm;
  }

  public final Insets getPlotInsets() {
    return insCA;
  }

  public final RunTimeContext getRunTimeContext() {
    return rtc;
  }

  public IChartComputation getChartComputation() {
    return cComp;
  }

  /**
   * Returns current rate for Point->Pixel.
   *
   * @return
   * @since 2.5
   */
  public double getPointToPixel() {
    return dPointToPixel;
  }
}