/*
 * 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 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 timber.log.Timber;

import java.util.ArrayList;

public abstract class ElementTimeImpl implements ElementTime {

    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
                    Timber.e(e, "Malformed time value.");
                }
            }
        }

        // "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");
        }
    }
}
