/**
 * Copyright 2019 Anthony Trinh
 *
 * 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 ch.qos.logback.core.pattern.parser;

import java.util.Map;

import ch.qos.logback.core.pattern.CompositeConverter;
import ch.qos.logback.core.pattern.Converter;
import ch.qos.logback.core.pattern.DynamicConverter;
import ch.qos.logback.core.pattern.LiteralConverter;
import ch.qos.logback.core.spi.ContextAwareBase;
import ch.qos.logback.core.status.ErrorStatus;
import ch.qos.logback.core.util.OptionHelper;

class Compiler<E> extends ContextAwareBase {

  Converter<E> head;
  Converter<E> tail;
  final Node top;
  final Map<String, String> converterMap;

  Compiler(final Node top, final Map<String, String> converterMap) {
    this.top = top;
    this.converterMap = converterMap;
  }

  Converter<E> compile() {
    head = tail = null;
    for (Node n = top; n != null; n = n.next) {
      switch (n.type) {
        case Node.LITERAL:
          addToList(new LiteralConverter<E>((String) n.getValue()));
          break;
        case Node.COMPOSITE_KEYWORD:
          CompositeNode cn = (CompositeNode) n;
          CompositeConverter<E> compositeConverter = createCompositeConverter(cn);
          if(compositeConverter == null) {
            addError("Failed to create converter for [%"+cn.getValue()+"] keyword");
            addToList(new LiteralConverter<E>("%PARSER_ERROR["+cn.getValue()+"]"));
            break;
          }
          compositeConverter.setFormattingInfo(cn.getFormatInfo());
          compositeConverter.setOptionList(cn.getOptions());
          Compiler<E> childCompiler = new Compiler<E>(cn.getChildNode(),
                  converterMap);
          childCompiler.setContext(context);
          Converter<E> childConverter = childCompiler.compile();
          compositeConverter.setChildConverter(childConverter);
          addToList(compositeConverter);
          break;
        case Node.SIMPLE_KEYWORD:
          SimpleKeywordNode kn = (SimpleKeywordNode) n;
          DynamicConverter<E> dynaConverter = createConverter(kn);
          if (dynaConverter != null) {
            dynaConverter.setFormattingInfo(kn.getFormatInfo());
            dynaConverter.setOptionList(kn.getOptions());
            addToList(dynaConverter);
          } else {
            // if the appropriate dynaconverter cannot be found, then replace
            // it with a dummy LiteralConverter indicating an error.
            Converter<E> errConveter = new LiteralConverter<E>("%PARSER_ERROR["
                    + kn.getValue() + "]");
            addStatus(new ErrorStatus("[" + kn.getValue()
                    + "] is not a valid conversion word", this));
            addToList(errConveter);
          }

      }
    }
    return head;
  }

  private void addToList(Converter<E> c) {
    if (head == null) {
      head = tail = c;
    } else {
      tail.setNext(c);
      tail = c;
    }
  }

  /**
   * Attempt to create a converter using the information found in
   * 'converterMap'.
   *
   * @param kn
   * @return
   */
  @SuppressWarnings("unchecked")
  DynamicConverter<E> createConverter(SimpleKeywordNode kn) {
    String keyword = (String) kn.getValue();
    String converterClassStr = (String) converterMap.get(keyword);

    if (converterClassStr != null) {
      try {
        return (DynamicConverter<E>) OptionHelper.instantiateByClassName(
                converterClassStr, DynamicConverter.class, context);
      } catch (Exception e) {
        addError("Failed to instantiate converter class [" + converterClassStr
                + "] for keyword ["+keyword+"]", e);
        return null;
      }
    } else {
      addError("There is no conversion class registered for conversion word ["
              + keyword + "]");
      return null;
    }
  }

  /**
   * Attempt to create a converter using the information found in
   * 'compositeConverterMap'.
   *
   * @param cn
   * @return
   */
  @SuppressWarnings("unchecked")
  CompositeConverter<E> createCompositeConverter(CompositeNode cn) {
    String keyword = (String) cn.getValue();
    String converterClassStr = (String) converterMap.get(keyword);

    if (converterClassStr != null) {
      try {
        return (CompositeConverter<E>) OptionHelper.instantiateByClassName(
                converterClassStr, CompositeConverter.class, context);
      } catch (Exception e) {
        addError("Failed to instantiate converter class [" + converterClassStr
                + "] as a composite converter for keyword ["+keyword+"]", e);
        return null;
      }
    } else {
      addError("There is no conversion class registered for composite conversion word ["
              + keyword + "]");
      return null;
    }
  }


  // public void setStatusManager(StatusManager statusManager) {
  // this.statusManager = statusManager;
  // }
  //  
  // void addStatus(Status status) {
  // if(statusManager != null) {
  // statusManager.add(status);
  // }
  // }
}