/*
 * Copyright (C) 2008 The Android Open Source Project
 *
 * 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.ddmuilib.log.event;

import com.android.ddmlib.log.EventContainer;
import com.android.ddmlib.log.EventLogParser;
import com.android.ddmlib.log.EventValueDescription;
import com.android.ddmlib.log.InvalidTypeException;
import com.android.ddmuilib.DdmUiPreferences;
import com.android.ddmuilib.TableHelper;

import org.eclipse.jface.preference.IPreferenceStore;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.ControlAdapter;
import org.eclipse.swt.events.ControlEvent;
import org.eclipse.swt.events.DisposeEvent;
import org.eclipse.swt.events.DisposeListener;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.ScrollBar;
import org.eclipse.swt.widgets.Table;
import org.eclipse.swt.widgets.TableColumn;
import org.eclipse.swt.widgets.TableItem;

import java.util.ArrayList;
import java.util.Calendar;

public class DisplayLog extends EventDisplay {
    public DisplayLog(String name) {
        super(name);
    }

    private final static String PREFS_COL_DATE = "EventLogPanel.log.Col1"; //$NON-NLS-1$
    private final static String PREFS_COL_PID = "EventLogPanel.log.Col2"; //$NON-NLS-1$
    private final static String PREFS_COL_EVENTTAG = "EventLogPanel.log.Col3"; //$NON-NLS-1$
    private final static String PREFS_COL_VALUENAME = "EventLogPanel.log.Col4"; //$NON-NLS-1$
    private final static String PREFS_COL_VALUE = "EventLogPanel.log.Col5"; //$NON-NLS-1$
    private final static String PREFS_COL_TYPE = "EventLogPanel.log.Col6"; //$NON-NLS-1$

    /**
     * Resets the display.
     */
    @Override
    void resetUI() {
        mLogTable.removeAll();
    }

    /**
     * Adds event to the display.
     */
    @Override
    void newEvent(EventContainer event, EventLogParser logParser) {
        addToLog(event, logParser);
    }

    /**
     * Creates the UI for the event display.
     *
     * @param parent    the parent composite.
     * @param logParser the current log parser.
     * @return the created control (which may have children).
     */
    @Override
    Control createComposite(Composite parent, EventLogParser logParser, ILogColumnListener listener) {
        return createLogUI(parent, listener);
    }

    /**
     * Adds an {@link EventContainer} to the log.
     *
     * @param event     the event.
     * @param logParser the log parser.
     */
    private void addToLog(EventContainer event, EventLogParser logParser) {
        ScrollBar bar = mLogTable.getVerticalBar();
        boolean scroll = bar.getMaximum() == bar.getSelection() + bar.getThumb();

        // get the date.
        Calendar c = Calendar.getInstance();
        long msec = event.sec * 1000L;
        c.setTimeInMillis(msec);

        // convert the time into a string
        String date = String.format("%1$tF %1$tT", c);

        String eventName = logParser.getTagMap().get(event.mTag);
        String pidName = Integer.toString(event.pid);

        // get the value description
        EventValueDescription[] valueDescription = logParser.getEventInfoMap().get(event.mTag);
        if (valueDescription != null) {
            for (int i = 0; i < valueDescription.length; i++) {
                EventValueDescription description = valueDescription[i];
                try {
                    String value = event.getValueAsString(i);

                    logValue(date, pidName, eventName, description.getName(), value,
                            description.getEventValueType(), description.getValueType());
                } catch (InvalidTypeException e) {
                    logValue(date, pidName, eventName, description.getName(), e.getMessage(),
                            description.getEventValueType(), description.getValueType());
                }
            }

            // scroll if needed, by showing the last item
            if (scroll) {
                int itemCount = mLogTable.getItemCount();
                if (itemCount > 0) {
                    mLogTable.showItem(mLogTable.getItem(itemCount - 1));
                }
            }
        }
    }

    /**
     * Adds an {@link EventContainer} to the log. Only add the values/occurrences defined by
     * the list of descriptors. If an event is configured to be displayed by value and occurrence,
     * only the values are displayed (as they mark an event occurrence anyway).
     * <p/>This method is only called when at least one of the descriptor list is non empty.
     *
     * @param event
     * @param logParser
     * @param valueDescriptors
     * @param occurrenceDescriptors
     */
    protected void addToLog(EventContainer event, EventLogParser logParser,
            ArrayList<ValueDisplayDescriptor> valueDescriptors,
            ArrayList<OccurrenceDisplayDescriptor> occurrenceDescriptors) {
        ScrollBar bar = mLogTable.getVerticalBar();
        boolean scroll = bar.getMaximum() == bar.getSelection() + bar.getThumb();

        // get the date.
        Calendar c = Calendar.getInstance();
        long msec = event.sec * 1000L;
        c.setTimeInMillis(msec);

        // convert the time into a string
        String date = String.format("%1$tF %1$tT", c);

        String eventName = logParser.getTagMap().get(event.mTag);
        String pidName = Integer.toString(event.pid);

        if (valueDescriptors.size() > 0) {
            for (ValueDisplayDescriptor descriptor : valueDescriptors) {
                logDescriptor(event, descriptor, date, pidName, eventName, logParser);
            }
        } else {
            // we display the event. Since the StringBuilder contains the header (date, event name,
            // pid) at this point, there isn't anything else to display.
        }

        // scroll if needed, by showing the last item
        if (scroll) {
            int itemCount = mLogTable.getItemCount();
            if (itemCount > 0) {
                mLogTable.showItem(mLogTable.getItem(itemCount - 1));
            }
        }
    }


    /**
     * Logs a value in the ui.
     *
     * @param date
     * @param pid
     * @param event
     * @param valueName
     * @param value
     * @param eventValueType
     * @param valueType
     */
    private void logValue(String date, String pid, String event, String valueName,
            String value, EventContainer.EventValueType eventValueType, EventValueDescription.ValueType valueType) {

        TableItem item = new TableItem(mLogTable, SWT.NONE);
        item.setText(0, date);
        item.setText(1, pid);
        item.setText(2, event);
        item.setText(3, valueName);
        item.setText(4, value);

        String type;
        if (valueType != EventValueDescription.ValueType.NOT_APPLICABLE) {
            type = String.format("%1$s, %2$s", eventValueType.toString(), valueType.toString());
        } else {
            type = eventValueType.toString();
        }

        item.setText(5, type);
    }

    /**
     * Logs a value from an {@link EventContainer} as defined by the {@link ValueDisplayDescriptor}.
     *
     * @param event      the EventContainer
     * @param descriptor the ValueDisplayDescriptor defining which value to display.
     * @param date       the date of the event in a string.
     * @param pidName
     * @param eventName
     * @param logParser
     */
    private void logDescriptor(EventContainer event, ValueDisplayDescriptor descriptor,
            String date, String pidName, String eventName, EventLogParser logParser) {

        String value;
        try {
            value = event.getValueAsString(descriptor.valueIndex);
        } catch (InvalidTypeException e) {
            value = e.getMessage();
        }

        EventValueDescription[] values = logParser.getEventInfoMap().get(event.mTag);

        EventValueDescription valueDescription = values[descriptor.valueIndex];

        logValue(date, pidName, eventName, descriptor.valueName, value,
                valueDescription.getEventValueType(), valueDescription.getValueType());
    }

    /**
     * Creates the UI for a log display.
     *
     * @param parent   the parent {@link Composite}
     * @param listener the {@link ILogColumnListener} to notify on column resize events.
     * @return the top Composite of the UI.
     */
    private Control createLogUI(Composite parent, final ILogColumnListener listener) {
        Composite mainComp = new Composite(parent, SWT.NONE);
        GridLayout gl;
        mainComp.setLayout(gl = new GridLayout(1, false));
        gl.marginHeight = gl.marginWidth = 0;
        mainComp.addDisposeListener(new DisposeListener() {
            @Override
            public void widgetDisposed(DisposeEvent e) {
                mLogTable = null;
            }
        });

        Label l = new Label(mainComp, SWT.CENTER);
        l.setText(mName);
        l.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));

        mLogTable = new Table(mainComp, SWT.MULTI | SWT.FULL_SELECTION | SWT.V_SCROLL |
                SWT.BORDER);
        mLogTable.setLayoutData(new GridData(GridData.FILL_BOTH));

        IPreferenceStore store = DdmUiPreferences.getStore();

        TableColumn col = TableHelper.createTableColumn(
                mLogTable, "Time",
                SWT.LEFT, "0000-00-00 00:00:00", PREFS_COL_DATE, store); //$NON-NLS-1$
        col.addControlListener(new ControlAdapter() {
            @Override
            public void controlResized(ControlEvent e) {
                Object source = e.getSource();
                if (source instanceof TableColumn) {
                    listener.columnResized(0, (TableColumn) source);
                }
            }
        });

        col = TableHelper.createTableColumn(
                mLogTable, "pid",
                SWT.LEFT, "0000", PREFS_COL_PID, store); //$NON-NLS-1$
        col.addControlListener(new ControlAdapter() {
            @Override
            public void controlResized(ControlEvent e) {
                Object source = e.getSource();
                if (source instanceof TableColumn) {
                    listener.columnResized(1, (TableColumn) source);
                }
            }
        });

        col = TableHelper.createTableColumn(
                mLogTable, "Event",
                SWT.LEFT, "abcdejghijklmno", PREFS_COL_EVENTTAG, store); //$NON-NLS-1$
        col.addControlListener(new ControlAdapter() {
            @Override
            public void controlResized(ControlEvent e) {
                Object source = e.getSource();
                if (source instanceof TableColumn) {
                    listener.columnResized(2, (TableColumn) source);
                }
            }
        });

        col = TableHelper.createTableColumn(
                mLogTable, "Name",
                SWT.LEFT, "Process Name", PREFS_COL_VALUENAME, store); //$NON-NLS-1$
        col.addControlListener(new ControlAdapter() {
            @Override
            public void controlResized(ControlEvent e) {
                Object source = e.getSource();
                if (source instanceof TableColumn) {
                    listener.columnResized(3, (TableColumn) source);
                }
            }
        });

        col = TableHelper.createTableColumn(
                mLogTable, "Value",
                SWT.LEFT, "0000000", PREFS_COL_VALUE, store); //$NON-NLS-1$
        col.addControlListener(new ControlAdapter() {
            @Override
            public void controlResized(ControlEvent e) {
                Object source = e.getSource();
                if (source instanceof TableColumn) {
                    listener.columnResized(4, (TableColumn) source);
                }
            }
        });

        col = TableHelper.createTableColumn(
                mLogTable, "Type",
                SWT.LEFT, "long, seconds", PREFS_COL_TYPE, store); //$NON-NLS-1$
        col.addControlListener(new ControlAdapter() {
            @Override
            public void controlResized(ControlEvent e) {
                Object source = e.getSource();
                if (source instanceof TableColumn) {
                    listener.columnResized(5, (TableColumn) source);
                }
            }
        });

        mLogTable.setHeaderVisible(true);
        mLogTable.setLinesVisible(true);

        return mainComp;
    }

    /**
     * Resizes the <code>index</code>-th column of the log {@link Table} (if applicable).
     * <p/>
     * This does nothing if the <code>Table</code> object is <code>null</code> (because the display
     * type does not use a column) or if the <code>index</code>-th column is in fact the originating
     * column passed as argument.
     *
     * @param index        the index of the column to resize
     * @param sourceColumn the original column that was resize, and on which we need to sync the
     *                     index-th column width.
     */
    @Override
    void resizeColumn(int index, TableColumn sourceColumn) {
        if (mLogTable != null) {
            TableColumn col = mLogTable.getColumn(index);
            if (col != sourceColumn) {
                col.setWidth(sourceColumn.getWidth());
            }
        }
    }

    /**
     * Gets display type
     *
     * @return display type as an integer
     */
    @Override
    int getDisplayType() {
        return DISPLAY_TYPE_LOG_ALL;
    }
}
