/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF 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 org.apache.iotdb.db.queryengine.transformation.dag.transformer.binary;

import org.apache.iotdb.db.exception.query.QueryProcessException;
import org.apache.iotdb.db.queryengine.transformation.api.LayerPointReader;
import org.apache.iotdb.db.queryengine.transformation.api.YieldableState;
import org.apache.iotdb.tsfile.exception.write.UnSupportedDataTypeException;
import org.apache.iotdb.tsfile.file.metadata.enums.TSDataType;

import java.io.IOException;

public abstract class LogicBinaryTransformer extends BinaryTransformer {

  protected LogicBinaryTransformer(
      LayerPointReader leftPointReader, LayerPointReader rightPointReader) {
    super(leftPointReader, rightPointReader);
  }

  @Override
  protected void checkType() {
    if (leftPointReaderDataType != TSDataType.BOOLEAN
        || rightPointReaderDataType != TSDataType.BOOLEAN) {
      throw new UnSupportedDataTypeException("Unsupported data type: " + TSDataType.BOOLEAN);
    }
  }

  @Override
  public YieldableState yieldValue() throws Exception {
    final YieldableState leftYieldableState = leftPointReader.yield();
    final YieldableState rightYieldableState = rightPointReader.yield();

    if (leftYieldableState == YieldableState.YIELDABLE
        && rightYieldableState == YieldableState.YIELDABLE) {
      cacheValue(leftPointReader, rightPointReader);
      return YieldableState.YIELDABLE;
    }

    if (leftYieldableState == YieldableState.NOT_YIELDABLE_WAITING_FOR_DATA
        || rightYieldableState == YieldableState.NOT_YIELDABLE_WAITING_FOR_DATA) {
      return YieldableState.NOT_YIELDABLE_WAITING_FOR_DATA;
    }

    if (leftYieldableState == YieldableState.NOT_YIELDABLE_NO_MORE_DATA
        && rightYieldableState == YieldableState.NOT_YIELDABLE_NO_MORE_DATA) {
      return YieldableState.NOT_YIELDABLE_NO_MORE_DATA;
    }

    if (leftYieldableState == YieldableState.YIELDABLE && !isLeftPointReaderConstant) {
      cacheValue(leftPointReader);
      return YieldableState.YIELDABLE;
    }
    if (rightYieldableState == YieldableState.YIELDABLE && !isRightPointReaderConstant) {
      cacheValue(rightPointReader);
      return YieldableState.YIELDABLE;
    }

    return YieldableState.NOT_YIELDABLE_NO_MORE_DATA;
  }

  @Override
  protected boolean cacheValue() throws QueryProcessException, IOException {
    final boolean leftHasNext = leftPointReader.next();
    final boolean rightHasNext = rightPointReader.next();

    if (leftHasNext && rightHasNext) {
      return cacheValue(leftPointReader, rightPointReader);
    }

    if (!leftHasNext && !rightHasNext) {
      return false;
    }

    if (leftHasNext && !isLeftPointReaderConstant) {
      return cacheValue(leftPointReader);
    }
    if (rightHasNext && !isRightPointReaderConstant) {
      return cacheValue(rightPointReader);
    }

    return false;
  }

  private boolean cacheValue(LayerPointReader reader) throws IOException {
    cachedTime = reader.currentTime();
    cachedBoolean = !reader.isCurrentNull() && evaluate(false, reader.currentBoolean());
    reader.readyForNext();
    return true;
  }

  private boolean cacheValue(LayerPointReader leftPointReader, LayerPointReader rightPointReader)
      throws IOException {
    final boolean leftBoolean =
        !leftPointReader.isCurrentNull() && leftPointReader.currentBoolean();
    final boolean rightBoolean =
        !rightPointReader.isCurrentNull() && rightPointReader.currentBoolean();

    if (isCurrentConstant) {
      cachedBoolean = evaluate(leftBoolean, rightBoolean);
      return true;
    }

    if (isLeftPointReaderConstant) {
      cachedTime = rightPointReader.currentTime();
      cachedBoolean = evaluate(leftBoolean, rightBoolean);
      rightPointReader.readyForNext();
      return true;
    }

    if (isRightPointReaderConstant) {
      cachedTime = leftPointReader.currentTime();
      cachedBoolean = evaluate(leftBoolean, rightBoolean);
      leftPointReader.readyForNext();
      return true;
    }

    final long leftTime = leftPointReader.currentTime();
    final long rightTime = rightPointReader.currentTime();

    if (leftTime < rightTime) {
      cachedTime = leftTime;
      cachedBoolean = evaluate(leftBoolean, false);
      leftPointReader.readyForNext();
      return true;
    }

    if (rightTime < leftTime) {
      cachedTime = rightTime;
      cachedBoolean = evaluate(false, rightBoolean);
      rightPointReader.readyForNext();
      return true;
    }

    // == rightTime
    cachedTime = leftTime;
    cachedBoolean = evaluate(leftBoolean, rightBoolean);
    leftPointReader.readyForNext();
    rightPointReader.readyForNext();
    return true;
  }

  protected abstract boolean evaluate(boolean leftOperand, boolean rightOperand);

  @Override
  protected void transformAndCache() throws QueryProcessException, IOException {
    throw new UnsupportedOperationException();
  }

  @Override
  public TSDataType getDataType() {
    return TSDataType.BOOLEAN;
  }
}
