content: /*
   * Copyright 2014 Jacob Klinker
   *
   * Licensed under the Apache License, Version 2.0 (the "License");
   * you may not use this file except in compliance with the License.
   * You may obtain a copy of the License at
   *
   *     http://www.apache.org/licenses/LICENSE-2.0
   *
   * Unless required by applicable law or agreed to in writing, software
   * distributed under the License is distributed on an "AS IS" BASIS,
   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   * See the License for the specific language governing permissions and
   * limitations under the License.
   */
  
  package com.android.mms.dom.smil;
  
  import com.android.mms.logs.LogTag;
  
  import java.util.ArrayList;
  
  import org.w3c.dom.DOMException;
  import org.w3c.dom.smil.ElementTime;
  import org.w3c.dom.smil.SMILElement;
  import org.w3c.dom.smil.Time;
  import org.w3c.dom.smil.TimeList;
  
  import com.klinker.android.logger.Log;
  
  public abstract class ElementTimeImpl implements ElementTime {
      private static final String TAG = LogTag.TAG;
  
      private static final String FILL_REMOVE_ATTRIBUTE = "remove";
      private static final String FILL_FREEZE_ATTRIBUTE = "freeze";
      private static final String FILL_HOLD_ATTRIBUTE = "hold";
      private static final String FILL_TRANSITION_ATTRIBUTE = "transition";
      private static final String FILL_AUTO_ATTRIBUTE   = "auto";
      private static final String FILL_ATTRIBUTE_NAME   = "fill";
      private static final String FILLDEFAULT_ATTRIBUTE_NAME   = "fillDefault";
  
      final SMILElement mSmilElement;
  
      /*
       * Internal Interface
       */
      ElementTimeImpl(SMILElement element) {
          mSmilElement = element;
      }
  
      // Default implementation. Override if required.
      int getBeginConstraints() {
          return TimeImpl.ALLOW_ALL;
      }
  
      // Default implementation. Override if required
      int getEndConstraints() {
          return TimeImpl.ALLOW_ALL;
      }
  
      /**
       * To get the parent node on the ElementTime tree. It is in opposition to getTimeChildren.
       * @return the parent ElementTime. Returns <code>null</code> if there is no parent.
       */
      abstract ElementTime getParentElementTime();
  
      /*
       * ElementTime Interface
       */
  
      public TimeList getBegin() {
          String[] beginTimeStringList = mSmilElement.getAttribute("begin").split(";");
  
          // TODO: Check other constraints on parsed values, e.g., "single, non-negative offset values
          ArrayList<Time> beginTimeList = new ArrayList<Time>();
          // Initialize Time instances and add them to Vector
          for (int i = 0; i < beginTimeStringList.length; i++) {
              try {
                  beginTimeList.add(new TimeImpl(beginTimeStringList[i], getBeginConstraints()));
              } catch (IllegalArgumentException e) {
                  // Ignore badly formatted times
              }
          }
          if (beginTimeList.size() == 0) {
              /*
               * What is the right default value?
               *
               * In MMS SMIL, this method may be called either on an instance of:
               *
               * 1 - ElementSequentialTimeContainer (The SMILDocument)
               * 2 - ElementParallelTimeContainer (A Time-Child of the SMILDocument, which is a seq)
               * 3 - ElementTime (A SMILMediaElement).
               *
               * 1 - In the first case, the default start time is obviously 0.
               * 2 - In the second case, the specifications mentions that
               *      "For children of a sequence, the only legal value for begin is
               *      a (single) non-negative offset value. The default begin value is 0."
               * 3 - In the third case, the specification mentions that
               *      "The default value of begin for children of a par is 0."
               *
               * In short, if no value is specified, the default is always 0.
               */
  
              beginTimeList.add(new TimeImpl("0", TimeImpl.ALLOW_ALL));
          }
          return new TimeListImpl(beginTimeList);
      }
  
      public float getDur() {
          float dur = 0;
          try {
              String durString = mSmilElement.getAttribute("dur");
              if (durString != null) {
                  dur = TimeImpl.parseClockValue(durString) / 1000f;
              }
          } catch (IllegalArgumentException e) {
              // Do nothing and return the minimum value
          }
  
          return dur;
      }
  
      public TimeList getEnd() {
          ArrayList<Time> endTimeList = new ArrayList<Time>();
  
          String[] endTimeStringList = mSmilElement.getAttribute("end").split(";");
          int len = endTimeStringList.length;
          if (!((len == 1) && (endTimeStringList[0].length() == 0))) {  // Ensure the end field is set.
              // Initialize Time instances and add them to Vector
              for (int i = 0; i < len; i++) {
                  try {
                      endTimeList.add(new TimeImpl(endTimeStringList[i],
                              getEndConstraints()));
                  } catch (IllegalArgumentException e) {
                      // Ignore badly formatted times
                      Log.e(TAG, "Malformed time value.", e);
                  }
              }
          }
  
          // "end" time is not specified
          if (endTimeList.size() == 0) {
              // Get duration
              float duration = getDur();
  
              if (duration < 0) {
                  endTimeList.add(new TimeImpl("indefinite", getEndConstraints()));
              } else {
                  // Get begin
                  TimeList begin = getBegin();
                  for (int i = 0; i < begin.getLength(); i++) {
                      endTimeList.add(new TimeImpl(
                              // end = begin + dur
                              begin.item(i).getResolvedOffset() + duration + "s",
                              getEndConstraints()));
                  }
              }
          }
  
          return new TimeListImpl(endTimeList);
      }
  
      private boolean beginAndEndAreZero() {
          TimeList begin = getBegin();
          TimeList end = getEnd();
          if (begin.getLength() == 1 && end.getLength() == 1) {
              Time beginTime = begin.item(0);
              Time endTime = end.item(0);
              return beginTime.getOffset() == 0. && endTime.getOffset() == 0.;
          }
          return false;
      }
  
      public short getFill() {
          String fill = mSmilElement.getAttribute(FILL_ATTRIBUTE_NAME);
          if (fill.equalsIgnoreCase(FILL_FREEZE_ATTRIBUTE)) {
              return FILL_FREEZE;
          } else if (fill.equalsIgnoreCase(FILL_REMOVE_ATTRIBUTE)) {
              return FILL_REMOVE;
          } else if (fill.equalsIgnoreCase(FILL_HOLD_ATTRIBUTE)) {
              // FIXME handle it as freeze for now
              return FILL_FREEZE;
          } else if (fill.equalsIgnoreCase(FILL_TRANSITION_ATTRIBUTE)) {
              // FIXME handle it as freeze for now
              return FILL_FREEZE;
          } else if (!fill.equalsIgnoreCase(FILL_AUTO_ATTRIBUTE)) {
              /*
               * fill = default
               * The fill behavior for the element is determined by the value of the fillDefault
               * attribute.  This is the default value.
               */
              short fillDefault = getFillDefault();
              if (fillDefault != FILL_AUTO) {
                  return fillDefault;
              }
          }
  
          /*
           * fill = auto
           * The fill behavior for this element depends on whether the element specifies any of
           * the attributes that define the simple or active duration:
           *  - If none of the attributes dur, end, repeatCount or repeatDur are specified on
           *    the element, then the element will have a fill behavior identical to that if it were
           *    specified as "freeze".
           *  - Otherwise, the element will have a fill behavior identical to that if it were
           *    specified as "remove".
           */
          if (((mSmilElement.getAttribute("dur").length() == 0) &&
                  (mSmilElement.getAttribute("end").length() == 0) &&
                  (mSmilElement.getAttribute("repeatCount").length() == 0) &&
                  (mSmilElement.getAttribute("repeatDur").length() == 0)) ||
                  beginAndEndAreZero()) {
              return FILL_FREEZE;
          } else {
              return FILL_REMOVE;
          }
      }
  
      public short getFillDefault() {
          String fillDefault = mSmilElement.getAttribute(FILLDEFAULT_ATTRIBUTE_NAME);
          if (fillDefault.equalsIgnoreCase(FILL_REMOVE_ATTRIBUTE)) {
              return FILL_REMOVE;
          } else if (fillDefault.equalsIgnoreCase(FILL_FREEZE_ATTRIBUTE)) {
              return FILL_FREEZE;
          } else if (fillDefault.equalsIgnoreCase(FILL_AUTO_ATTRIBUTE)) {
              return FILL_AUTO;
          } else if (fillDefault.equalsIgnoreCase(FILL_HOLD_ATTRIBUTE)) {
              // FIXME handle it as freeze for now
              return FILL_FREEZE;
          } else if (fillDefault.equalsIgnoreCase(FILL_TRANSITION_ATTRIBUTE)) {
              // FIXME handle it as freeze for now
              return FILL_FREEZE;
          } else {
              /*
               * fillDefault = inherit
               * Specifies that the value of this attribute (and of the fill behavior) are
               * inherited from the fillDefault value of the parent element.
               * This is the default value.
               */
              ElementTime parent = getParentElementTime();
              if (parent == null) {
                  /*
                   * fillDefault = auto
                   * If there is no parent element, the value is "auto".
                   */
                  return FILL_AUTO;
              } else {
                  return ((ElementTimeImpl) parent).getFillDefault();
              }
          }
      }
  
      public float getRepeatCount() {
          String repeatCount = mSmilElement.getAttribute("repeatCount");
          try {
              float value = Float.parseFloat(repeatCount);
              if (value > 0) {
                  return value;
              } else {
                  return 0; // default
              }
          } catch (NumberFormatException e) {
              return 0; // default
          }
      }
  
      public float getRepeatDur() {
          try {
              float repeatDur =
                  TimeImpl.parseClockValue(mSmilElement.getAttribute("repeatDur"));
              if (repeatDur > 0) {
                  return repeatDur;
              } else {
                  return 0; // default
              }
          } catch (IllegalArgumentException e) {
              return 0; // default
          }
      }
  
      public short getRestart() {
          String restart = mSmilElement.getAttribute("restart");
          if (restart.equalsIgnoreCase("never")) {
              return RESTART_NEVER;
          } else if (restart.equalsIgnoreCase("whenNotActive")) {
              return RESTART_WHEN_NOT_ACTIVE;
          } else {
              return RESTART_ALWAYS; // default
          }
      }
  
      public void setBegin(TimeList begin) throws DOMException {
          // TODO Implement this
          mSmilElement.setAttribute("begin", "indefinite");
      }
  
      public void setDur(float dur) throws DOMException {
          // In SMIL 3.0, the dur could be a timecount-value which may contain fractions.
          // However, in MMS 1.3, the dur SHALL be expressed in integer milliseconds.
          mSmilElement.setAttribute("dur", Integer.toString((int)(dur * 1000)) + "ms");
      }
  
      public void setEnd(TimeList end) throws DOMException {
          // TODO Implement this
          mSmilElement.setAttribute("end", "indefinite");
      }
  
      public void setFill(short fill) throws DOMException {
          if (fill == FILL_FREEZE) {
              mSmilElement.setAttribute(FILL_ATTRIBUTE_NAME, FILL_FREEZE_ATTRIBUTE);
          } else {
              mSmilElement.setAttribute(FILL_ATTRIBUTE_NAME, FILL_REMOVE_ATTRIBUTE); // default
          }
      }
  
      public void setFillDefault(short fillDefault) throws DOMException {
          if (fillDefault == FILL_FREEZE) {
              mSmilElement.setAttribute(FILLDEFAULT_ATTRIBUTE_NAME, FILL_FREEZE_ATTRIBUTE);
          } else {
              mSmilElement.setAttribute(FILLDEFAULT_ATTRIBUTE_NAME, FILL_REMOVE_ATTRIBUTE);
          }
      }
  
      public void setRepeatCount(float repeatCount) throws DOMException {
          String repeatCountString = "indefinite";
          if (repeatCount > 0) {
              repeatCountString = Float.toString(repeatCount);
          }
          mSmilElement.setAttribute("repeatCount", repeatCountString);
      }
  
      public void setRepeatDur(float repeatDur) throws DOMException {
          String repeatDurString = "indefinite";
          if (repeatDur > 0) {
              repeatDurString = Float.toString(repeatDur) + "ms";
          }
          mSmilElement.setAttribute("repeatDur", repeatDurString);
      }
  
      public void setRestart(short restart) throws DOMException {
          if (restart == RESTART_NEVER) {
              mSmilElement.setAttribute("restart", "never");
          } else if (restart == RESTART_WHEN_NOT_ACTIVE) {
              mSmilElement.setAttribute("restart", "whenNotActive");
          } else {
              mSmilElement.setAttribute("restart", "always");
          }
      }
  }
  