/*
 * Copyright (C) 2003-2010 eXo Platform SAS.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Affero General Public License
 * as published by the Free Software Foundation; either version 3
 * of the License, or (at your option) any later version.
 *
 * This program 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, see<http://www.gnu.org/licenses/>.
 */
package org.exoplatform.wiki.service.wysiwyg;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

import javax.inject.Inject;

import org.exoplatform.services.log.ExoLogger;
import org.exoplatform.services.log.Log;
import org.exoplatform.wiki.rendering.filter.MacroFilter;
import org.xwiki.component.manager.ComponentLookupException;
import org.xwiki.component.manager.ComponentManager;
import org.xwiki.gwt.wysiwyg.client.plugin.macro.MacroDescriptor;
import org.xwiki.gwt.wysiwyg.client.plugin.macro.MacroService;
import org.xwiki.gwt.wysiwyg.client.plugin.macro.ParameterDescriptor;
import org.xwiki.gwt.wysiwyg.client.plugin.macro.ParameterType;
import org.xwiki.rendering.macro.Macro;
import org.xwiki.rendering.macro.MacroCategoryManager;
import org.xwiki.rendering.macro.MacroId;
import org.xwiki.rendering.macro.MacroManager;
import org.xwiki.rendering.syntax.Syntax;
import org.xwiki.rendering.syntax.SyntaxFactory;

public class WikiMacroService implements MacroService {

  private static Log                log = ExoLogger.getLogger("wiki:WikiMacroService");

  /**
   * The syntax factory used to create {@link Syntax} instances from string
   * syntax identifiers.
   */
  @Inject
  private SyntaxFactory             syntaxFactory;

  /**
   * The macro manager used to retrieve macros.
   */
  @Inject
  private MacroManager              macroManager;

  /**
   * The macro category manager used to retrieve macro categories.
   */
  @Inject
  private MacroCategoryManager      categoryManager;
  
  /**
   * The component manager used to macro filter
   */
  @Inject
  private ComponentManager componentManager;

  /**
   * The component used to translate macro descriptors into the execution
   * context language.
   */
  @Inject
  private MacroDescriptorTranslator macroDescriptorTranslator;

  /**
   * {@inheritDoc}
   * 
   * @see MacroService#getMacroDescriptor(String, String)
   */
  public MacroDescriptor getMacroDescriptor(String macroId, String syntaxId) {
    return macroDescriptorTranslator.translate(getUntranslatedMacroDescriptor(macroId, syntaxId));
  }

  /**
   * @param macroId the macro identifier
   * @param syntaxId the syntax identifier
   * @return the untranslated macro descriptor for the specified macro
   */
  private MacroDescriptor getUntranslatedMacroDescriptor(String macroId, String syntaxId) {
    try {
      MacroId macroIdObject = new MacroId(macroId, syntaxFactory.createSyntaxFromIdString(syntaxId));
      Macro<?> macro = macroManager.getMacro(macroIdObject);
      org.xwiki.rendering.macro.descriptor.MacroDescriptor descriptor = macro.getDescriptor();

      ParameterDescriptor contentDescriptor = null;
      if (descriptor.getContentDescriptor() != null) {
        contentDescriptor = new ParameterDescriptor();
        contentDescriptor.setId("content");
        contentDescriptor.setName("Content");
        contentDescriptor.setDescription(descriptor.getContentDescriptor().getDescription());
        // Just a hack to distinguish between regular strings and large strings.
        contentDescriptor.setType(createMacroParameterType(StringBuffer.class));
        contentDescriptor.setMandatory(descriptor.getContentDescriptor().isMandatory());
      }

      // We use a linked hash map to preserve the order of the macro parameters.
      Map<String, ParameterDescriptor> parameterDescriptorMap = new LinkedHashMap<String, ParameterDescriptor>();
      for (Map.Entry<String, org.xwiki.rendering.macro.descriptor.ParameterDescriptor> entry : descriptor.getParameterDescriptorMap()
                                                                                                         .entrySet()) {
        parameterDescriptorMap.put(entry.getKey(), createMacroParameterDescriptor(entry.getValue()));
      }

      MacroDescriptor result = new MacroDescriptor();
      result.setId(macroIdObject.getId());
      result.setName(descriptor.getName());
      result.setDescription(descriptor.getDescription());
      result.setSupportingInlineMode(macro.supportsInlineMode());
      // NOTE: we should set the category also, but we need a new method in
      // MacroCategoryManager.
      result.setContentDescriptor(contentDescriptor);
      result.setParameterDescriptorMap(parameterDescriptorMap);

      return result;
    } catch (Exception e) {
      log.error("Exception while retrieving macro descriptor.", e);
      throw new RuntimeException(e.getLocalizedMessage());
    }
  }

  /**
   * Creates a {@link ParameterDescriptor} from a
   * {@link org.xwiki.rendering.macro.descriptor.ParameterDescriptor}.
   * 
   * @param descriptor a macro parameter descriptor from the rendering package
   * @return a macro parameter descriptor from the WYSIWYG package
   */
  private ParameterDescriptor createMacroParameterDescriptor(org.xwiki.rendering.macro.descriptor.ParameterDescriptor descriptor) {
    ParameterDescriptor result = new ParameterDescriptor();
    result.setId(descriptor.getId());
    result.setName(descriptor.getName());
    result.setDescription(descriptor.getDescription());
    result.setType(createMacroParameterType(descriptor.getType()));
    Object defaultValue = descriptor.getDefaultValue();
    if (defaultValue != null) {
      result.setDefaultValue(String.valueOf(defaultValue));
    }
    result.setMandatory(descriptor.isMandatory());
    return result;
  }

  /**
   * NOTE: We can't send a {@link Class} instance to the client side because GWT
   * can't serialize it so we have to convert it to a {@link ParameterType}
   * instance.
   * 
   * @param parameterClass a {@link Class} that defines the values a macro
   *          parameter can have
   * @return the parameter type associated with the given {@link Class} instance
   */
  private ParameterType createMacroParameterType(Class<?> parameterClass) {
    ParameterType parameterType = new ParameterType();
    parameterType.setName(parameterClass.getName());
    if (parameterClass.isEnum()) {
      Object[] parameterClassConstants = parameterClass.getEnumConstants();
      Map<String, String> parameterTypeConstants = new LinkedHashMap<String, String>();
      for (int i = 0; i < parameterClassConstants.length; i++) {
        String constant = String.valueOf(parameterClassConstants[i]);
        // We leave the constant unlocalized for now.
        parameterTypeConstants.put(constant, constant);
      }
      parameterType.setEnumConstants(parameterTypeConstants);
    }
    return parameterType;
  }

  /**
   * {@inheritDoc}
   * 
   * @see MacroService#getMacroDescriptors(String)
   */
  public List<MacroDescriptor> getMacroDescriptors(String syntaxId) {
    try {
      Syntax syntax = syntaxFactory.createSyntaxFromIdString(syntaxId);
      List<MacroDescriptor> descriptors = new ArrayList<MacroDescriptor>();
      for (String category : categoryManager.getMacroCategories(syntax)) {
        for (MacroId macroId : categoryManager.getMacroIds(category, syntax)) {
          if (!"table".equals(macroId.getId()) && !"thead".equals(macroId.getId())
              && !"th".equals(macroId.getId()) && !"table-row".equals(macroId.getId())
              && !"table-cell".equals(macroId.getId()) && !"column".equals(macroId.getId())) {
            boolean isSupported = true;
            try {
              MacroFilter macroFilter = componentManager.getInstance(MacroFilter.class, syntaxId);
              isSupported = macroFilter.isSupport(macroId.getId());
            } catch (ComponentLookupException e) {
              if (log.isDebugEnabled()) {
                log.debug(String.format("Syntax %s doesn't have any macro filter", syntaxId));
              }
            }
            if (isSupported) {
              MacroDescriptor descriptor = getUntranslatedMacroDescriptor(macroId.getId(), syntaxId);
              descriptor.setCategory(category);
              descriptors.add(macroDescriptorTranslator.translate(descriptor));
            }
          }
        }
      }

      Collections.sort(descriptors, new Comparator<MacroDescriptor>() {
        public int compare(MacroDescriptor alice, MacroDescriptor bob) {
          return alice.getName().compareTo(bob.getName());
        }
      });

      return descriptors;
    } catch (Exception e) {
      log.error(String.format("Exception while retrieving the list of macro descriptors for syntax %s.",
                              syntaxId),
                e);
      throw new RuntimeException(e.getLocalizedMessage());
    }
  }

}
