// Copyright (c) 2010 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

package org.chromium.debug.ui.actions;

import java.util.AbstractList;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.chromium.debug.core.model.ConnectedTargetData;
import org.chromium.debug.core.util.ScriptTargetMapping;
import org.chromium.debug.ui.TableUtils;
import org.chromium.debug.ui.TableUtils.ColumnBasedLabelProvider;
import org.chromium.debug.ui.TableUtils.ColumnData;
import org.chromium.debug.ui.TableUtils.ColumnLabelProvider;
import org.chromium.debug.ui.TableUtils.ValueAdapter;
import org.eclipse.debug.ui.DebugUITools;
import org.eclipse.jface.resource.ImageDescriptor;
import org.eclipse.jface.viewers.CheckStateChangedEvent;
import org.eclipse.jface.viewers.CheckboxTableViewer;
import org.eclipse.jface.viewers.ICheckStateListener;
import org.eclipse.jface.viewers.IStructuredContentProvider;
import org.eclipse.jface.viewers.Viewer;
import org.eclipse.swt.SWT;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Table;
import org.eclipse.swt.widgets.TableColumn;

/**
 * A UI control that presents the list of connections with remote V8 VMs. User may choose
 * several VMs.
 */
public class ChooseVmControl {
  public static Logic create(Composite parent) {
    final Table table = new Table(parent, SWT.CHECK | SWT.BORDER | SWT.MULTI | SWT.FULL_SELECTION);

    table.setFont(parent.getFont());

    final CheckboxTableViewer tableViewer = new CheckboxTableViewer(table);

    table.setHeaderVisible(true);

    tableViewer.setContentProvider(new ContentProviderImpl());

    ValueAdapter<ScriptTargetMapping, ConnectedTargetData> pairToTargetAdapter =
        new ValueAdapter<ScriptTargetMapping, ConnectedTargetData>() {
          public ConnectedTargetData convert(ScriptTargetMapping from) {
            return from.getConnectedTargetData();
          }
        };


    List<ColumnData<ScriptTargetMapping, ?>> columnDataList =
        createLaunchTargetColumns(pairToTargetAdapter);

    for (ColumnData<?,?> data : columnDataList) {
      data.getLabelProvider().createColumn(table);
    }

    ValueAdapter<Object, ScriptTargetMapping> rowElementAdapter =
        TableUtils.createCastAdapter(ScriptTargetMapping.class);

    ColumnBasedLabelProvider<ScriptTargetMapping> labelProvider =
        new ColumnBasedLabelProvider<ScriptTargetMapping>(rowElementAdapter, columnDataList);

    tableViewer.setLabelProvider(labelProvider);

    final List<Logic.Listener> listeners = new ArrayList<Logic.Listener>(1);

    tableViewer.addCheckStateListener(new ICheckStateListener() {
      public void checkStateChanged(CheckStateChangedEvent event) {
        for (Logic.Listener listener : listeners) {
          listener.checkStateChanged();
        }
      }
    });

    return new Logic() {
      public Control getControl() {
        return table;
      }

      public void setData(List<? extends ScriptTargetMapping> targets) {
        TableData input = new TableData(targets);
        tableViewer.setInput(input);
      }

      public List<ScriptTargetMapping> getSelected() {
        final Object[] array = tableViewer.getCheckedElements();
        return new AbstractList<ScriptTargetMapping>() {
          @Override
          public ScriptTargetMapping get(int index) {
            return (ScriptTargetMapping) array[index];
          }
          @Override
          public int size() {
            return array.length;
          }
        };
      }

      public void selectAll() {
        tableViewer.setAllChecked(true);
      }

      public void addListener(Listener listener) {
        listeners.add(listener);
      }

      public void removeListener(Listener listener) {
        listeners.remove(listener);
      }
    };
  }

  /**
   * A public logic-oriented interface to this control.
   */
  public interface Logic {
    Control getControl();

    void setData(List<? extends ScriptTargetMapping> targets);
    List<ScriptTargetMapping> getSelected();

    void selectAll();

    void addListener(Listener listener);
    void removeListener(Listener listener);

    interface Listener {
      void checkStateChanged();
    }
  }

  /**
   * A non-generic class that wraps generic List. Eclipse works in terms of Objects, but you cannot
   * safely cast to generic types on runtime (e.g. to List<String>) so we wrap the value before
   * passing it into Eclipse.
   */
  private static class TableData {
    final List<? extends ScriptTargetMapping> targets;

    TableData(List<? extends ScriptTargetMapping> targets) {
      this.targets = targets;
    }
  }

  private static class ContentProviderImpl implements IStructuredContentProvider {
    public Object[] getElements(Object inputElement) {
      TableData input = (TableData) inputElement;
      return input.targets.toArray();
    }

    public void dispose() {}

    public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {}
  }

  private static class LaunchNameLabelProvider extends ColumnLabelProvider<ConnectedTargetData> {
    private Map<ConnectedTargetData, Image> createdImages =
        new HashMap<ConnectedTargetData, Image>();

    @Override
    public Image getColumnImage(ConnectedTargetData connectedTargetData) {
      Image result = createdImages.get(connectedTargetData);
      if (result == null) {
        ImageDescriptor imageDescriptor = DebugUITools.getDefaultImageDescriptor(
            connectedTargetData.getDebugTarget().getLaunch().getLaunchConfiguration());
        result = imageDescriptor.createImage();
        createdImages.put(connectedTargetData, result);
      }
      return result;
    }

    @Override
    public String getColumnText(ConnectedTargetData connectedTargetData) {
      return connectedTargetData.getDebugTarget().getLaunch().getLaunchConfiguration().getName();
    }

    @Override
    public TableColumn createColumn(Table table) {
      TableColumn launchCol = new TableColumn(table, SWT.NONE);
      launchCol.setText(Messages.ChooseVmControl_LAUNCH);
      launchCol.setWidth(200);
      return launchCol;
    }

    @Override
    public void dispose() {
      for (Image image : createdImages.values()) {
        image.dispose();
      }
    }
  }

  private static class TargetNameLabelProvider extends ColumnLabelProvider<ConnectedTargetData> {
    @Override
    public Image getColumnImage(ConnectedTargetData connectedTargetData) {
      return null;
    }

    @Override
    public String getColumnText(ConnectedTargetData connectedTargetData) {
      return connectedTargetData.getName();
    }

    @Override
    public TableColumn createColumn(Table table) {
      TableColumn targetCol = new TableColumn(table, SWT.NONE);
      targetCol.setText(Messages.ChooseVmControl_TARGET);
      targetCol.setWidth(200);
      return targetCol;
    }
  }

  /**
   * Returns the list of column providers for launch/target columns that could be used in other
   * controls; they only have to provide there row->column value adapter.
   */
  public static <R> List<ColumnData<R, ?>> createLaunchTargetColumns(
      ValueAdapter<R, ConnectedTargetData> rowValueAdapter) {
    List<ColumnData<R, ?>> result = new ArrayList<ColumnData<R, ?>>(2);

    result.add(ColumnData.create(rowValueAdapter, new LaunchNameLabelProvider()));
    result.add(ColumnData.create(rowValueAdapter, new TargetNameLabelProvider()));

    return result;
  }
}
