package org.asciidoctor.jruby.internal;

import org.asciidoctor.extension.BlockMacroProcessor;
import org.asciidoctor.extension.BlockProcessor;
import org.asciidoctor.extension.DocinfoProcessor;
import org.asciidoctor.extension.ExtensionGroup;
import org.asciidoctor.extension.IncludeProcessor;
import org.asciidoctor.extension.InlineMacroProcessor;
import org.asciidoctor.extension.Postprocessor;
import org.asciidoctor.extension.Preprocessor;
import org.asciidoctor.extension.Treeprocessor;
import org.asciidoctor.jruby.extension.processorproxies.AbstractProcessorProxy;
import org.asciidoctor.jruby.extension.processorproxies.BlockMacroProcessorProxy;
import org.asciidoctor.jruby.extension.processorproxies.BlockProcessorProxy;
import org.asciidoctor.jruby.extension.processorproxies.DocinfoProcessorProxy;
import org.asciidoctor.jruby.extension.processorproxies.IncludeProcessorProxy;
import org.asciidoctor.jruby.extension.processorproxies.InlineMacroProcessorProxy;
import org.asciidoctor.jruby.extension.processorproxies.PostprocessorProxy;
import org.asciidoctor.jruby.extension.processorproxies.PreprocessorProxy;
import org.asciidoctor.jruby.extension.processorproxies.TreeprocessorProxy;
import org.jruby.Ruby;
import org.jruby.RubyClass;
import org.jruby.RubyModule;
import org.jruby.RubyObject;
import org.jruby.anno.JRubyMethod;
import org.jruby.javasupport.JavaEmbedUtils;
import org.jruby.runtime.Block;
import org.jruby.runtime.ObjectAllocator;
import org.jruby.runtime.ThreadContext;
import org.jruby.runtime.builtin.IRubyObject;

import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;

/**
 * Created by robertpanzer on 21.07.17.
 */
public class ExtensionGroupImpl implements ExtensionGroup {

  private JRubyAsciidoctor asciidoctor;

  private final Ruby rubyRuntime;

  private final String groupName;

  private final RubyModule asciidoctorModule;
  private final RubyClass extensionGroupClass;

  private List<Registrator> registrators = new ArrayList<>();

  public ExtensionGroupImpl(String groupName, JRubyAsciidoctor asciidoctor, RubyClass extensionGroupClass) {
    this.groupName = groupName;
    this.asciidoctor = asciidoctor;
    this.rubyRuntime = asciidoctor.getRubyRuntime();
    this.asciidoctorModule = rubyRuntime.getModule("AsciidoctorModule");
    this.extensionGroupClass = extensionGroupClass;
  }

  public String getGroupName() {
    return groupName;
  }

  @Override
  public void register() {
    IRubyObject callback = extensionGroupClass.newInstance(rubyRuntime.getCurrentContext(), Block.NULL_BLOCK);
    asciidoctorModule.callMethod("register_extension_group",
        rubyRuntime.newString(this.groupName),
        callback,
        JavaEmbedUtils.javaToRuby(rubyRuntime, registrators));
  }

  static RubyClass createExtensionGroupClass(final Ruby rubyRuntime) {
    final RubyClass extensionGroupClass = rubyRuntime.getModule("AsciidoctorModule")
        .defineClassUnder("ExtensionGroupImpl", rubyRuntime.getObject(), new ObjectAllocator() {
          @Override
          public IRubyObject allocate(Ruby runtime, RubyClass klazz) {
            return new ExtensionGroupRegistrationCallback(runtime, klazz);
          }
        });
    extensionGroupClass.defineAnnotatedMethods(ExtensionGroupRegistrationCallback.class);
    return extensionGroupClass;
  }

  public static class ExtensionGroupRegistrationCallback extends RubyObject {

    private List<Registrator> registrators = new ArrayList<>();

    ExtensionGroupRegistrationCallback(Ruby runtime, RubyClass metaClass) {
      super(runtime, metaClass);
    }

    @JRubyMethod(name = "register_extensions", required = 2)
    public IRubyObject registerExtensions(ThreadContext context, IRubyObject registry, IRubyObject rubyRegistrators) {
      List<Registrator> registrators = (List<Registrator>) JavaEmbedUtils.rubyToJava(rubyRegistrators);
      for (Registrator registrator: registrators) {
        registrator.register(registry);
      }
      return context.getRuntime().getNil();
    }
  }

  @Override
  public void unregister() {
    asciidoctorModule
        .callMethod("unregister_extension", rubyRuntime.newString(this.groupName));
  }

  @Override
  public ExtensionGroup docinfoProcessor(final Class<? extends DocinfoProcessor> docInfoProcessor) {
    final RubyClass rubyClass = DocinfoProcessorProxy.register(asciidoctor, docInfoProcessor);
    registrators.add(new Registrator() {
      @Override
      public void register(IRubyObject registry) {
        registry.callMethod(rubyRuntime.getCurrentContext(), "docinfo_processor", rubyClass);
      }
    });
    return this;
  }

  @Override
  public ExtensionGroup docinfoProcessor(final DocinfoProcessor docInfoProcessor) {
    final RubyClass rubyClass = DocinfoProcessorProxy.register(asciidoctor, docInfoProcessor);
    registrators.add(new Registrator() {
      @Override
      public void register(IRubyObject registry) {
        registry.callMethod(rubyRuntime.getCurrentContext(), "docinfo_processor", rubyClass);
      }
    });
    return this;
  }

  @Override
  public ExtensionGroup docinfoProcessor(final String docInfoProcessor) {
    try {
      final Class<? extends DocinfoProcessor>  docinfoProcessorClass = (Class<? extends DocinfoProcessor>) Class.forName(docInfoProcessor);
      docinfoProcessor(docinfoProcessorClass);
    } catch (ClassNotFoundException e) {
      throw new RuntimeException(e);
    }
    return this;
  }

  @Override
  public ExtensionGroup preprocessor(final Class<? extends Preprocessor> preprocessor) {
    final RubyClass rubyClass = PreprocessorProxy.register(asciidoctor, preprocessor);
    registrators.add(new Registrator() {
      @Override
      public void register(IRubyObject registry) {
        registry.callMethod(rubyRuntime.getCurrentContext(), "preprocessor", rubyClass);
      }
    });
    return this;
  }

  @Override
  public ExtensionGroup preprocessor(final Preprocessor preprocessor) {
    final RubyClass rubyClass = PreprocessorProxy.register(asciidoctor, preprocessor);
    registrators.add(new Registrator() {
      @Override
      public void register(IRubyObject registry) {
        registry.callMethod(rubyRuntime.getCurrentContext(), "preprocessor", rubyClass);
      }
    });
    return this;
  }

  @Override
  public ExtensionGroup preprocessor(final String preprocessor) {
    try {
      final Class<? extends Preprocessor>  preprocessorClass = (Class<? extends Preprocessor>) Class.forName(preprocessor);
      preprocessor(preprocessorClass);
    } catch (ClassNotFoundException e) {
      throw new RuntimeException(e);
    }
    return this;
  }

  @Override
  public ExtensionGroup postprocessor(final String postprocessor) {
    try {
        Class<? extends Postprocessor>  postprocessorClass = (Class<? extends Postprocessor>) Class.forName(postprocessor);
        postprocessor(postprocessorClass);
    } catch (ClassNotFoundException e) {
      throw new RuntimeException(e);
    }
    return this;
  }

  @Override
  public ExtensionGroup postprocessor(final Class<? extends Postprocessor> postprocessor) {
    final RubyClass rubyClass = PostprocessorProxy.register(asciidoctor, postprocessor);
    registrators.add(new Registrator() {
      @Override
      public void register(IRubyObject registry) {
        registry.callMethod(rubyRuntime.getCurrentContext(), "postprocessor", rubyClass);
      }
    });
    return this;
  }

  @Override
  public ExtensionGroup postprocessor(final Postprocessor postprocessor) {
    final RubyClass rubyClass = PostprocessorProxy.register(asciidoctor, postprocessor);
    registrators.add(new Registrator() {
      @Override
      public void register(IRubyObject registry) {
        registry.callMethod(rubyRuntime.getCurrentContext(), "postprocessor", rubyClass);
      }
    });
    return this;
  }

  @Override
  public ExtensionGroup includeProcessor(final String includeProcessor) {
    try {
      Class<? extends IncludeProcessor>  includeProcessorClass = (Class<? extends IncludeProcessor>) Class.forName(includeProcessor);
      includeProcessor(includeProcessorClass);
      return this;
    } catch (ClassNotFoundException e) {
      throw new RuntimeException(e);
    }
  }

  @Override
  public ExtensionGroup includeProcessor(final Class<? extends IncludeProcessor> includeProcessor) {
    final RubyClass rubyClass = IncludeProcessorProxy.register(asciidoctor, includeProcessor);
    registrators.add(new Registrator() {
      @Override
      public void register(IRubyObject registry) {
        registry.callMethod(rubyRuntime.getCurrentContext(), "include_processor", rubyClass);
      }
    });
    return this;
  }

  @Override
  public ExtensionGroup includeProcessor(final IncludeProcessor includeProcessor) {
    final RubyClass rubyClass = IncludeProcessorProxy.register(asciidoctor, includeProcessor);
    registrators.add(new Registrator() {
      @Override
      public void register(IRubyObject registry) {
        registry.callMethod(rubyRuntime.getCurrentContext(), "include_processor", rubyClass);
      }
    });
    return this;
  }

  @Override
  public ExtensionGroup treeprocessor(final Treeprocessor treeprocessor) {
    final RubyClass rubyClass = TreeprocessorProxy.register(asciidoctor, treeprocessor);
    registrators.add(new Registrator() {
      @Override
      public void register(IRubyObject registry) {
        registry.callMethod(rubyRuntime.getCurrentContext(), "tree_processor", rubyClass);
      }
    });
    return this;
  }

  @Override
  public ExtensionGroup treeprocessor(final Class<? extends Treeprocessor> treeProcessor) {
    final RubyClass rubyClass = TreeprocessorProxy.register(asciidoctor, treeProcessor);
    registrators.add(new Registrator() {
      @Override
      public void register(IRubyObject registry) {
        registry.callMethod(rubyRuntime.getCurrentContext(), "tree_processor", rubyClass);
      }
    });
    return this;
  }

  @Override
  public ExtensionGroup treeprocessor(final String treeProcessor) {
    try {
      Class<? extends Treeprocessor>  treeProcessorClass = (Class<? extends Treeprocessor>) Class.forName(treeProcessor);
      treeprocessor(treeProcessorClass);
      return this;
    } catch (ClassNotFoundException e) {
      throw new RuntimeException(e);
    }
  }

  @Override
  public ExtensionGroup block(final String blockName, final String blockProcessor) {
    try {
      Class<? extends BlockProcessor>  blockProcessorClass = (Class<? extends BlockProcessor>) Class.forName(blockProcessor);
      return block(blockName, blockProcessorClass);
    } catch (ClassNotFoundException e) {
      throw new RuntimeException(e);
    }
  }

  @Override
  public ExtensionGroup block(final String blockProcessor) {
    try {
      Class<? extends BlockProcessor>  blockProcessorClass = (Class<? extends BlockProcessor>) Class.forName(blockProcessor);
      return block(blockProcessorClass);
    } catch (ClassNotFoundException e) {
      throw new RuntimeException(e);
    }
  }

  @Override
  public ExtensionGroup block(final String blockName, final Class<? extends BlockProcessor> blockProcessor) {
    final RubyClass rubyClass = BlockProcessorProxy.register(asciidoctor, blockProcessor);
    registrators.add(new Registrator() {
      @Override
      public void register(IRubyObject registry) {
        registry.callMethod(rubyRuntime.getCurrentContext(), "block", new IRubyObject[]{rubyClass, rubyRuntime.newSymbol(blockName)});
      }
    });
    return this;
  }

  @Override
  public ExtensionGroup block(final Class<? extends BlockProcessor> blockProcessor) {
    final RubyClass rubyClass = BlockProcessorProxy.register(asciidoctor, blockProcessor);
    registrators.add(new Registrator() {
      @Override
      public void register(IRubyObject registry) {
        registry.callMethod(rubyRuntime.getCurrentContext(), "block", new IRubyObject[]{rubyClass, rubyRuntime.newSymbol(BlockProcessorProxy.getName(blockProcessor))});
      }
    });
    return this;
  }

  @Override
  public ExtensionGroup block(final BlockProcessor blockProcessor) {
    final RubyClass rubyClass = BlockProcessorProxy.register(asciidoctor, blockProcessor);
    registrators.add(new Registrator() {
      @Override
      public void register(IRubyObject registry) {
        registry.callMethod(rubyRuntime.getCurrentContext(), "block", new IRubyObject[]{rubyClass, rubyRuntime.newSymbol(blockProcessor.getName())});
      }
    });
    return this;
  }

  @Override
  public ExtensionGroup block(final String blockName, final BlockProcessor blockProcessor) {
    final RubyClass rubyClass = BlockProcessorProxy.register(asciidoctor, blockProcessor);
    registrators.add(new Registrator() {
      @Override
      public void register(IRubyObject registry) {
        registry.callMethod(rubyRuntime.getCurrentContext(), "block", new IRubyObject[]{rubyClass, rubyRuntime.newSymbol(blockName)});
      }
    });
    return this;
  }

  @Override
  public ExtensionGroup blockMacro(final String blockName, final Class<? extends BlockMacroProcessor> blockMacroProcessor) {
    final RubyClass rubyClass = BlockMacroProcessorProxy.register(asciidoctor, blockMacroProcessor);
    registrators.add(new Registrator() {
      @Override
      public void register(IRubyObject registry) {
        registry.callMethod(rubyRuntime.getCurrentContext(), "block_macro", new IRubyObject[]{rubyClass, rubyRuntime.newSymbol(blockName)});
      }
    });
    return this;
  }

  @Override
  public ExtensionGroup blockMacro(final Class<? extends BlockMacroProcessor> blockMacroProcessor) {
    final RubyClass rubyClass = BlockMacroProcessorProxy.register(asciidoctor, blockMacroProcessor);
    registrators.add(new Registrator() {
      @Override
      public void register(IRubyObject registry) {
        registry.callMethod(rubyRuntime.getCurrentContext(), "block_macro", new IRubyObject[]{rubyClass, rubyRuntime.newSymbol(AbstractProcessorProxy.getName(blockMacroProcessor))});
      }
    });
    return this;
  }

  @Override
  public ExtensionGroup blockMacro(final String blockName, final String blockMacroProcessor) {
    try {
      Class<? extends BlockMacroProcessor>  blockMacroProcessorClass = (Class<? extends BlockMacroProcessor>) Class.forName(blockMacroProcessor);
      return blockMacro(blockName, blockMacroProcessorClass);
    } catch (ClassNotFoundException e) {
      throw new RuntimeException(e);
    }
  }

  @Override
  public ExtensionGroup blockMacro(final String blockMacroProcessor) {
    try {
      Class<? extends BlockMacroProcessor>  blockMacroProcessorClass = (Class<? extends BlockMacroProcessor>) Class.forName(blockMacroProcessor);
      return blockMacro(blockMacroProcessorClass);
    } catch (ClassNotFoundException e) {
      throw new RuntimeException(e);
    }
  }

  @Override
  public ExtensionGroup blockMacro(final BlockMacroProcessor blockMacroProcessor) {
    final RubyClass rubyClass = BlockMacroProcessorProxy.register(asciidoctor, blockMacroProcessor);
    registrators.add(new Registrator() {
      @Override
      public void register(IRubyObject registry) {
        registry.callMethod(rubyRuntime.getCurrentContext(), "block_macro", new IRubyObject[]{rubyClass, rubyRuntime.newSymbol(blockMacroProcessor.getName())});
      }
    });
    return this;
  }

  @Override
  public ExtensionGroup inlineMacro(final InlineMacroProcessor inlineMacroProcessor) {
    final RubyClass rubyClass = InlineMacroProcessorProxy.register(asciidoctor, inlineMacroProcessor);
    registrators.add(new Registrator() {
      @Override
      public void register(IRubyObject registry) {
        registry.callMethod(rubyRuntime.getCurrentContext(), "inline_macro", new IRubyObject[]{rubyClass, rubyRuntime.newSymbol(inlineMacroProcessor.getName())});
      }
    });
    return this;
  }

  @Override
  public ExtensionGroup inlineMacro(final String name, final Class<? extends InlineMacroProcessor> inlineMacroProcessor) {
    final RubyClass rubyClass = InlineMacroProcessorProxy.register(asciidoctor, inlineMacroProcessor);
    registrators.add(new Registrator() {
      @Override
      public void register(IRubyObject registry) {
        registry.callMethod(rubyRuntime.getCurrentContext(), "inline_macro", new IRubyObject[]{rubyClass, rubyRuntime.newSymbol(name)});
      }
    });
    return this;
  }

  @Override
  public ExtensionGroup inlineMacro(final Class<? extends InlineMacroProcessor> inlineMacroProcessor) {
    final RubyClass rubyClass = InlineMacroProcessorProxy.register(asciidoctor, inlineMacroProcessor);
    registrators.add(new Registrator() {
      @Override
      public void register(IRubyObject registry) {
        registry.callMethod(rubyRuntime.getCurrentContext(), "inline_macro", new IRubyObject[]{rubyClass, rubyRuntime.newSymbol(AbstractProcessorProxy.getName(inlineMacroProcessor))});
      }
    });
    return this;
  }

  @Override
  public ExtensionGroup inlineMacro(final String name, final String inlineMacroProcessor) {
    try {
      Class<? extends InlineMacroProcessor>  inlineMacroProcessorClass = (Class<? extends InlineMacroProcessor>) Class.forName(inlineMacroProcessor);
      return inlineMacro(name, inlineMacroProcessorClass);
    } catch (ClassNotFoundException e) {
      throw new RuntimeException(e);
    }
  }

  @Override
  public ExtensionGroup inlineMacro(final String inlineMacroProcessor) {
    try {
      Class<? extends InlineMacroProcessor>  inlineMacroProcessorClass = (Class<? extends InlineMacroProcessor>) Class.forName(inlineMacroProcessor);
      return inlineMacro(inlineMacroProcessorClass);
    } catch (ClassNotFoundException e) {
      throw new RuntimeException(e);
    }
  }

  @Override
  public ExtensionGroup requireRubyLibrary(final String requiredLibrary) {
    RubyUtils.requireLibrary(rubyRuntime, requiredLibrary);
    return this;
  }

  @Override
  public ExtensionGroup loadRubyClass(final InputStream rubyClassStream) {
    RubyUtils.loadRubyClass(rubyRuntime, rubyClassStream);
    return this;
  }

  @Override
  public ExtensionGroup rubyPreprocessor(final String preprocessor) {
    registrators.add(new Registrator() {
      @Override
      public void register(IRubyObject registry) {
        registry.callMethod(rubyRuntime.getCurrentContext(), "preprocessor", new IRubyObject[]{rubyRuntime.newString(preprocessor)});
      }
    });
    return this;
  }

  @Override
  public ExtensionGroup rubyPostprocessor(final String postprocessor) {
    registrators.add(new Registrator() {
      @Override
      public void register(IRubyObject registry) {
        registry.callMethod(rubyRuntime.getCurrentContext(), "postprocessor", new IRubyObject[]{rubyRuntime.newString(postprocessor)});
      }
    });
    return this;
  }

  @Override
  public ExtensionGroup rubyDocinfoProcessor(final String docinfoProcessor) {
    registrators.add(new Registrator() {
      @Override
      public void register(IRubyObject registry) {
        registry.callMethod(rubyRuntime.getCurrentContext(), "docinfo_processor", new IRubyObject[]{rubyRuntime.newString(docinfoProcessor)});
      }
    });
    return this;
  }

  @Override
  public ExtensionGroup rubyIncludeProcessor(final String includeProcessor) {
    registrators.add(new Registrator() {
      @Override
      public void register(IRubyObject registry) {
        registry.callMethod(rubyRuntime.getCurrentContext(), "include_processor", new IRubyObject[]{rubyRuntime.newString(includeProcessor)});
      }
    });
    return this;
  }

  @Override
  public ExtensionGroup rubyTreeprocessor(final String treeProcessor) {
    registrators.add(new Registrator() {
      @Override
      public void register(IRubyObject registry) {
        registry.callMethod(rubyRuntime.getCurrentContext(), "tree_processor", new IRubyObject[]{rubyRuntime.newString(treeProcessor)});
      }
    });
    return this;
  }

  @Override
  public ExtensionGroup rubyBlock(final String blockName, final String blockProcessor) {
    registrators.add(new Registrator() {
      @Override
      public void register(IRubyObject registry) {
        registry.callMethod(rubyRuntime.getCurrentContext(), "block", new IRubyObject[]{rubyRuntime.newString(blockProcessor), rubyRuntime.newSymbol(blockName)});
      }
    });
    return this;
  }

  @Override
  public ExtensionGroup rubyBlock(final String blockProcessor) {
    registrators.add(new Registrator() {
      @Override
      public void register(IRubyObject registry) {
        registry.callMethod(rubyRuntime.getCurrentContext(), "block", new IRubyObject[]{rubyRuntime.newString(blockProcessor)});
      }
    });
    return this;
  }

  @Override
  public ExtensionGroup rubyBlockMacro(final String blockName, final String blockMacroProcessor) {
    registrators.add(new Registrator() {
      @Override
      public void register(IRubyObject registry) {
        registry.callMethod(rubyRuntime.getCurrentContext(), "block_macro", new IRubyObject[]{rubyRuntime.newString(blockMacroProcessor), rubyRuntime.newSymbol(blockName)});
      }
    });
    return this;
  }

  @Override
  public ExtensionGroup rubyBlockMacro(final String blockMacroProcessor) {
    registrators.add(new Registrator() {
      @Override
      public void register(IRubyObject registry) {
        registry.callMethod(rubyRuntime.getCurrentContext(), "block_macro", new IRubyObject[]{rubyRuntime.newString(blockMacroProcessor)});
      }
    });
    return this;
  }

  @Override
  public ExtensionGroup rubyInlineMacro(final String macroName, final String inlineMacroProcessor) {
    registrators.add(new Registrator() {
      @Override
      public void register(IRubyObject registry) {
        registry.callMethod(rubyRuntime.getCurrentContext(), "inline_macro", new IRubyObject[]{rubyRuntime.newString(inlineMacroProcessor), rubyRuntime.newSymbol(macroName)});
      }
    });
    return this;
  }

  @Override
  public ExtensionGroup rubyInlineMacro(final String inlineMacroProcessor) {
    registrators.add(new Registrator() {
      @Override
      public void register(IRubyObject registry) {
        registry.callMethod(rubyRuntime.getCurrentContext(), "inline_macro", new IRubyObject[]{rubyRuntime.newString(inlineMacroProcessor)});
      }
    });
    return this;
  }

  public interface Registrator {
    void register(IRubyObject registry);
  }
}