/*
 * Licensed to Julian Hyde under one or more contributor license
 * agreements.  See the NOTICE file distributed with this work
 * for additional information regarding copyright ownership.
 * Julian Hyde licenses this file to you 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 net.hydromatic.morel.compile;

import java.util.List;
import java.util.function.Consumer;
import net.hydromatic.morel.ast.Core;
import net.hydromatic.morel.eval.Code;
import org.checkerframework.checker.nullness.qual.Nullable;

/** Utilities for {@link Tracer}. */
public abstract class Tracers {

  /** Returns a tracer that does nothing. */
  public static Tracer empty() {
    return EmptyTracer.INSTANCE;
  }

  /**
   * Returns a tracer that performs the given action on a declaration, then
   * calls the underlying tracer.
   */
  public static Tracer withOnCore(
      Tracer tracer, int pass, Consumer<Core.Decl> consumer) {
    final int expectedPass = pass;
    return new DelegatingTracer(tracer) {
      @Override
      public void onCore(int pass, Core.Decl e) {
        if (pass == expectedPass) {
          consumer.accept(e);
        }
        super.onCore(pass, e);
      }
    };
  }

  /**
   * Returns a tracer that performs the given action on code, then calls the
   * underlying tracer.
   */
  public static Tracer withOnPlan(Tracer tracer, Consumer<Code> consumer) {
    return new DelegatingTracer(tracer) {
      @Override
      public void onPlan(Code code) {
        consumer.accept(code);
        super.onPlan(code);
      }
    };
  }

  /**
   * Returns a tracer that performs the given action on the result of an
   * evaluation, then calls the underlying tracer.
   */
  public static Tracer withOnResult(Tracer tracer, Consumer<Object> consumer) {
    return new DelegatingTracer(tracer) {
      @Override
      public void onResult(Object o) {
        consumer.accept(o);
        super.onResult(o);
      }
    };
  }

  public static Tracer withOnWarnings(
      Tracer tracer, Consumer<List<Throwable>> consumer) {
    return new DelegatingTracer(tracer) {
      @Override
      public void onWarnings(List<Throwable> warningList) {
        consumer.accept(warningList);
        super.onWarnings(warningList);
      }
    };
  }

  public static Tracer withOnException(
      Tracer tracer, Consumer<@Nullable Throwable> consumer) {
    return new DelegatingTracer(tracer) {
      @Override
      public boolean onException(@Nullable Throwable e) {
        consumer.accept(e);
        super.onException(e);
        return true;
      }
    };
  }

  public static Tracer withOnCompileException(
      Tracer tracer, Consumer<CompileException> consumer) {
    return new DelegatingTracer(tracer) {
      @Override
      public boolean handleCompileException(@Nullable CompileException e) {
        consumer.accept(e);
        super.handleCompileException(e);
        return true;
      }
    };
  }

  public static Tracer withOnTypeException(
      Tracer tracer, Consumer<TypeResolver.TypeException> consumer) {
    return new DelegatingTracer(tracer) {
      @Override
      public boolean onTypeException(TypeResolver.TypeException e) {
        consumer.accept(e);
        super.onTypeException(e);
        return true;
      }
    };
  }

  /** Tracer that does nothing. */
  private static class EmptyTracer implements Tracer {
    static final Tracer INSTANCE = new EmptyTracer();

    @Override
    public void onCore(int pass, Core.Decl e) {}

    @Override
    public void onPlan(Code code) {}

    @Override
    public void onResult(Object o) {}

    @Override
    public void onWarnings(List<Throwable> warningList) {}

    @Override
    public boolean onTypeException(TypeResolver.TypeException e) {
      return false;
    }

    @Override
    public boolean onException(@Nullable Throwable e) {
      return false;
    }

    @Override
    public boolean handleCompileException(@Nullable CompileException e) {
      return false;
    }
  }

  /** Tracer that delegates to an underlying tracer. */
  private static class DelegatingTracer implements Tracer {
    final Tracer tracer;

    DelegatingTracer(Tracer tracer) {
      this.tracer = tracer;
    }

    @Override
    public void onCore(int pass, Core.Decl e) {
      tracer.onCore(pass, e);
    }

    @Override
    public void onPlan(Code code) {
      tracer.onPlan(code);
    }

    @Override
    public void onResult(Object o) {
      tracer.onResult(o);
    }

    @Override
    public void onWarnings(List<Throwable> warningList) {
      tracer.onWarnings(warningList);
    }

    @Override
    public boolean onException(@Nullable Throwable e) {
      return tracer.onException(e);
    }

    @Override
    public boolean onTypeException(TypeResolver.TypeException e) {
      return tracer.onTypeException(e);
    }

    @Override
    public boolean handleCompileException(@Nullable CompileException e) {
      return tracer.handleCompileException(e);
    }
  }
}

// End Tracers.java
