/*
 * JBoss, Home of Professional Open Source.
 * Copyright 2014, Red Hat Middleware LLC, and individual contributors
 * as indicated by the @author tags. See the copyright.txt file in the
 * distribution for a full listing of individual contributors.
 *
 * This 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 (at your option) any later version.
 *
 * This software 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 software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 */

package org.jboss.as.ejb3.timerservice.persistence.filestore;

import org.jboss.as.ejb3.logging.EjbLogger;
import org.jboss.as.ejb3.timerservice.CalendarTimer;
import org.jboss.as.ejb3.timerservice.TimerImpl;
import org.jboss.marshalling.Marshaller;
import org.jboss.marshalling.MarshallerFactory;
import org.jboss.marshalling.MarshallingConfiguration;
import org.jboss.marshalling.OutputStreamByteOutput;
import org.jboss.staxmapper.XMLElementWriter;
import org.jboss.staxmapper.XMLExtendedStreamWriter;
import org.jboss.util.Base64;

import javax.xml.stream.XMLStreamException;
import java.io.ByteArrayOutputStream;
import java.util.List;

/**
 * @author Stuart Douglas
 */
public class EjbTimerXmlPersister implements XMLElementWriter<List<TimerImpl>> {

    static final String TIMERS = "timers";
    static final String TIMER = "timer";
    static final String CALENDAR_TIMER = "calendar-timer";
    static final String INFO = "info";
    static final String PRIMARY_KEY = "primary-key";
    static final String TIMED_OBJECT_ID = "timed-object-id";
    static final String TIMER_ID = "timer-id";
    static final String INITIAL_DATE = "initial-date";
    static final String REPEAT_INTERVAL = "repeat-interval";
    static final String NEXT_DATE = "next-date";
    static final String PREVIOUS_RUN = "previous-run";
    static final String TIMER_STATE = "timer-state";
    static final String TIMEOUT_METHOD = "timeout-method";
    static final String SCHEDULE_EXPR_SECOND = "schedule-expr-second";
    static final String SCHEDULE_EXPR_MINUTE = "schedule-expr-minute";
    static final String SCHEDULE_EXPR_HOUR = "schedule-expr-hour";
    static final String SCHEDULE_EXPR_DAY_OF_WEEK = "schedule-expr-day-of-week";
    static final String SCHEDULE_EXPR_DAY_OF_MONTH = "schedule-expr-day-of-month";
    static final String SCHEDULE_EXPR_MONTH = "schedule-expr-month";
    static final String SCHEDULE_EXPR_YEAR = "schedule-expr-year";
    static final String SCHEDULE_EXPR_START_DATE = "schedule-expr-start-date";
    static final String SCHEDULE_EXPR_END_DATE = "schedule-expr-end-date";
    static final String SCHEDULE_EXPR_TIMEZONE = "schedule-expr-timezone";
    static final String PARAMETER = "parameter";
    static final String DECLARING_CLASS = "declaring-class";
    static final String NAME = "name";
    static final String TYPE = "type";

    private final MarshallerFactory factory;
    private final MarshallingConfiguration configuration;

    public EjbTimerXmlPersister(MarshallerFactory factory, MarshallingConfiguration configuration) {
        this.factory = factory;
        this.configuration = configuration;
    }

    @Override
    public void writeContent(XMLExtendedStreamWriter writer, List<TimerImpl> timers) throws XMLStreamException {

        writer.writeStartDocument();
        writer.writeStartElement(TIMERS);
        writer.writeDefaultNamespace(EjbTimerXmlParser_1_0.NAMESPACE);
        for (TimerImpl timer : timers) {
            if (timer instanceof CalendarTimer) {
                writeCalendarTimer(writer, (CalendarTimer) timer);
            } else {
                writeTimer(writer, timer);
            }
        }
        writer.writeEndElement();
        writer.writeEndDocument();
    }


    private void writeCalendarTimer(XMLExtendedStreamWriter writer, CalendarTimer timer) throws XMLStreamException {
        String info = null;
        String primaryKey = null;
        if (timer.getInfo() != null) {
            try {
                Marshaller marshaller = factory.createMarshaller(configuration);
                ByteArrayOutputStream out = new ByteArrayOutputStream();
                marshaller.start(new OutputStreamByteOutput(out));
                marshaller.writeObject(timer.getInfo());
                marshaller.finish();
                marshaller.flush();
                info = Base64.encodeBytes(out.toByteArray());
            } catch (Exception e) {
                EjbLogger.ROOT_LOGGER.failedToPersistTimer(timer, e);
                return;
            }
        }
        if (timer.getPrimaryKey() != null) {
            try {
                Marshaller marshaller = factory.createMarshaller(configuration);
                ByteArrayOutputStream out = new ByteArrayOutputStream();
                marshaller.start(new OutputStreamByteOutput(out));
                marshaller.writeObject(timer.getPrimaryKey());
                marshaller.finish();
                marshaller.flush();
                primaryKey = Base64.encodeBytes(out.toByteArray());
            } catch (Exception e) {
                EjbLogger.ROOT_LOGGER.failedToPersistTimer(timer, e);
                return;
            }
        }
        writer.writeStartElement(CALENDAR_TIMER);
        writer.writeAttribute(TIMED_OBJECT_ID, timer.getTimedObjectId());
        writer.writeAttribute(TIMER_ID, timer.getId());
        if (timer.getInitialExpiration() != null) {
            writer.writeAttribute(INITIAL_DATE, Long.toString(timer.getInitialExpiration().getTime()));
        }
        if(timer.getNextExpiration() != null) {
            writer.writeAttribute(NEXT_DATE, Long.toString(timer.getNextExpiration().getTime()));
        }
        writer.writeAttribute(TIMER_STATE, timer.getState().name());

        writer.writeAttribute(SCHEDULE_EXPR_SECOND, timer.getScheduleExpression().getSecond());
        writer.writeAttribute(SCHEDULE_EXPR_MINUTE, timer.getScheduleExpression().getMinute());
        writer.writeAttribute(SCHEDULE_EXPR_HOUR, timer.getScheduleExpression().getHour());
        writer.writeAttribute(SCHEDULE_EXPR_DAY_OF_WEEK, timer.getScheduleExpression().getDayOfWeek());
        writer.writeAttribute(SCHEDULE_EXPR_DAY_OF_MONTH, timer.getScheduleExpression().getDayOfMonth());
        writer.writeAttribute(SCHEDULE_EXPR_MONTH, timer.getScheduleExpression().getMonth());
        writer.writeAttribute(SCHEDULE_EXPR_YEAR, timer.getScheduleExpression().getYear());
        if (timer.getScheduleExpression().getStart() != null) {
            writer.writeAttribute(SCHEDULE_EXPR_START_DATE, Long.toString(timer.getScheduleExpression().getStart().getTime()));
        }
        if (timer.getScheduleExpression().getEnd() != null) {
            writer.writeAttribute(SCHEDULE_EXPR_END_DATE, Long.toString(timer.getScheduleExpression().getEnd().getTime()));
        }
        if (timer.getScheduleExpression().getTimezone() != null) {
            writer.writeAttribute(SCHEDULE_EXPR_TIMEZONE, timer.getScheduleExpression().getTimezone());
        }

        if (info != null) {
            writer.writeStartElement(INFO);
            writer.writeCharacters(info);
            writer.writeEndElement();
        }
        if (primaryKey != null) {
            writer.writeStartElement(PRIMARY_KEY);
            writer.writeCharacters(primaryKey);
            writer.writeEndElement();
        }
        if (timer.isAutoTimer()) {
            writer.writeStartElement(TIMEOUT_METHOD);
            writer.writeAttribute(DECLARING_CLASS, timer.getTimeoutMethod().getDeclaringClass().getName());
            writer.writeAttribute(NAME, timer.getTimeoutMethod().getName());
            for (Class<?> param : timer.getTimeoutMethod().getParameterTypes()) {
                writer.writeStartElement(PARAMETER);
                writer.writeAttribute(TYPE, param.getName());
                writer.writeEndElement();
            }
            writer.writeEndElement();
        }
        writer.writeEndElement();
    }

    private void writeTimer(XMLExtendedStreamWriter writer, TimerImpl timer) throws XMLStreamException {
        String info = null;
        String primaryKey = null;
        if (timer.getInfo() != null) {
            try {
                Marshaller marshaller = factory.createMarshaller(configuration);
                ByteArrayOutputStream out = new ByteArrayOutputStream();
                marshaller.start(new OutputStreamByteOutput(out));
                marshaller.writeObject(timer.getInfo());
                marshaller.finish();
                marshaller.flush();
                info = Base64.encodeBytes(out.toByteArray());
            } catch (Exception e) {
                EjbLogger.ROOT_LOGGER.failedToPersistTimer(timer, e);
                return;
            }
        }
        if (timer.getPrimaryKey() != null) {
            try {
                Marshaller marshaller = factory.createMarshaller(configuration);
                ByteArrayOutputStream out = new ByteArrayOutputStream();
                marshaller.start(new OutputStreamByteOutput(out));
                marshaller.writeObject(timer.getPrimaryKey());
                marshaller.finish();
                marshaller.flush();
                primaryKey = Base64.encodeBytes(out.toByteArray());
            } catch (Exception e) {
                EjbLogger.ROOT_LOGGER.failedToPersistTimer(timer, e);
                return;
            }
        }
        writer.writeStartElement(TIMER);
        writer.writeAttribute(TIMED_OBJECT_ID, timer.getTimedObjectId());
        writer.writeAttribute(TIMER_ID, timer.getId());
        writer.writeAttribute(INITIAL_DATE, Long.toString(timer.getInitialExpiration().getTime()));
        writer.writeAttribute(REPEAT_INTERVAL, Long.toString(timer.getInterval()));
        if(timer.getNextExpiration() != null) {
            writer.writeAttribute(NEXT_DATE, Long.toString(timer.getNextExpiration().getTime()));
        }
        writer.writeAttribute(TIMER_STATE, timer.getState().name());
        if (info != null) {
            writer.writeStartElement(INFO);
            writer.writeCharacters(info);
            writer.writeEndElement();
        }
        if (primaryKey != null) {
            writer.writeStartElement(PRIMARY_KEY);
            writer.writeCharacters(primaryKey);
            writer.writeEndElement();
        }
        writer.writeEndElement();
    }
}
