/**
 * 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.hadoop.yarn.server.timelineservice.reader;

import java.util.List;

/**
 * Used for encoding/decoding UID which will be used for query by UI.
 */
enum TimelineUIDConverter {
  // Flow UID should contain cluster, user and flow name.
  FLOW_UID {
    @Override
    String encodeUID(TimelineReaderContext context) {
      if (context == null) {
        return null;
      }
      if (context.getClusterId() == null || context.getUserId() == null ||
          context.getFlowName() == null) {
        return null;
      }
      String[] flowNameTupleArr = {context.getClusterId(), context.getUserId(),
          context.getFlowName()};
      return joinAndEscapeUIDParts(flowNameTupleArr);
    }

    @Override
    TimelineReaderContext decodeUID(String uId) throws Exception {
      if (uId == null) {
        return null;
      }
      List<String> flowNameTupleList = splitUID(uId);
      // Should have 3 parts i.e. cluster, user and flow name.
      if (flowNameTupleList.size() != 3) {
        return null;
      }
      return new TimelineReaderContext(flowNameTupleList.get(0),
          flowNameTupleList.get(1), flowNameTupleList.get(2), null,
          null, null, null);
    }
  },

  // Flowrun UID should contain cluster, user, flow name and flowrun id.
  FLOWRUN_UID{
    @Override
    String encodeUID(TimelineReaderContext context) {
      if (context == null) {
        return null;
      }
      if (context.getClusterId() == null || context.getUserId() == null ||
          context.getFlowName() == null || context.getFlowRunId() == null) {
        return null;
      }
      String[] flowRunTupleArr = {context.getClusterId(), context.getUserId(),
          context.getFlowName(), context.getFlowRunId().toString()};
      return joinAndEscapeUIDParts(flowRunTupleArr);
    }

    @Override
    TimelineReaderContext decodeUID(String uId) throws Exception {
      if (uId == null) {
        return null;
      }
      List<String> flowRunTupleList = splitUID(uId);
      // Should have 4 parts i.e. cluster, user, flow name and flowrun id.
      if (flowRunTupleList.size() != 4) {
        return null;
      }
      return new TimelineReaderContext(flowRunTupleList.get(0),
          flowRunTupleList.get(1), flowRunTupleList.get(2),
          Long.parseLong(flowRunTupleList.get(3)), null, null, null);
    }
  },

  // Application UID should contain cluster, user, flow name, flowrun id
  // and app id OR cluster and app id(i.e.without flow context info).
  APPLICATION_UID{
    @Override
    String encodeUID(TimelineReaderContext context) {
      if (context == null) {
        return null;
      }
      if (context.getClusterId() == null || context.getAppId() == null) {
        return null;
      }
      if (context.getUserId() != null && context.getFlowName() != null &&
          context.getFlowRunId() != null) {
        // Flow information exists.
        String[] appTupleArr = {context.getClusterId(), context.getUserId(),
            context.getFlowName(), context.getFlowRunId().toString(),
            context.getAppId()};
        return joinAndEscapeUIDParts(appTupleArr);
      } else {
        // Only cluster and app information exists. Flow info does not exist.
        String[] appTupleArr = {context.getClusterId(), context.getAppId()};
        return joinAndEscapeUIDParts(appTupleArr);
      }
    }

    @Override
    TimelineReaderContext decodeUID(String uId) throws Exception {
      if (uId == null) {
        return null;
      }
      List<String> appTupleList = splitUID(uId);
      // Should have 5 parts i.e. cluster, user, flow name, flowrun id
      // and app id OR should have 2 parts i.e. cluster and app id.
      if (appTupleList.size() == 5) {
        // Flow information exists.
        return new TimelineReaderContext(appTupleList.get(0),
            appTupleList.get(1), appTupleList.get(2),
            Long.parseLong(appTupleList.get(3)), appTupleList.get(4),
            null, null);
      } else if (appTupleList.size() == 2) {
        // Flow information does not exist.
        return new TimelineReaderContext(appTupleList.get(0), null, null, null,
            appTupleList.get(1), null, null);
      } else {
        return null;
      }
    }
  },

  // Generic Entity UID should contain cluster, user, flow name, flowrun id,
  // app id, entity type and entity id OR should contain cluster, appid, entity
  // type and entity id(i.e.without flow context info).
  GENERIC_ENTITY_UID {
    @Override
    String encodeUID(TimelineReaderContext context) {
      if (context == null) {
        return null;
      }
      if (context.getClusterId() == null || context.getAppId() == null ||
          context.getEntityType() == null || context.getEntityId() == null) {
        return null;
      }
      if (context.getUserId() != null && context.getFlowName() != null &&
          context.getFlowRunId() != null) {
        // Flow information exists.
        String[] entityTupleArr = {context.getClusterId(), context.getUserId(),
            context.getFlowName(), context.getFlowRunId().toString(),
            context.getAppId(), context.getEntityType(), context.getEntityId()};
        return joinAndEscapeUIDParts(entityTupleArr);
      } else {
        // Only entity and app information exists. Flow info does not exist.
        String[] entityTupleArr = {context.getClusterId(), context.getAppId(),
            context.getEntityType(), context.getEntityId()};
        return joinAndEscapeUIDParts(entityTupleArr);
      }
    }

    @Override
    TimelineReaderContext decodeUID(String uId) throws Exception {
      if (uId == null) {
        return null;
      }
      List<String> entityTupleList = splitUID(uId);
      // Should have 7 parts i.e. cluster, user, flow name, flowrun id, app id,
      // entity type and entity id OR should have 4 parts i.e. cluster, app id,
      // entity type and entity id.
      if (entityTupleList.size() == 7) {
        // Flow information exists.
        return new TimelineReaderContext(entityTupleList.get(0),
            entityTupleList.get(1), entityTupleList.get(2),
            Long.parseLong(entityTupleList.get(3)), entityTupleList.get(4),
            entityTupleList.get(5), entityTupleList.get(6));
      } else if (entityTupleList.size() == 4) {
        // Flow information does not exist.
        return new TimelineReaderContext(entityTupleList.get(0), null, null,
            null, entityTupleList.get(1), entityTupleList.get(2),
            entityTupleList.get(3));
      } else {
        return null;
      }
    }
  };

  /**
   * Delimiter used for UID.
   */
  public static final char UID_DELIMITER_CHAR = '!';

  /**
   * Escape Character used if delimiter or escape character itself is part of
   * different components of UID.
   */
  public static final char UID_ESCAPE_CHAR = '*';

  /**
   * Split UID using {@link #UID_DELIMITER_CHAR} and {@link #UID_ESCAPE_CHAR}.
   * @param uid UID to be splitted.
   * @return a list of different parts of UID split across delimiter.
   * @throws IllegalArgumentException if UID is not properly escaped.
   */
  private static List<String> splitUID(String uid)
      throws IllegalArgumentException {
    return TimelineReaderUtils.split(uid, UID_DELIMITER_CHAR, UID_ESCAPE_CHAR);
  }

  /**
   * Join different parts of UID delimited by {@link #UID_DELIMITER_CHAR} with
   * delimiter and escape character escaped using {@link #UID_ESCAPE_CHAR} if
   * UID parts contain them.
   * @param parts an array of UID parts to be joined.
   * @return a string joined using the delimiter with escape and delimiter
   *     characters escaped if they are part of the string parts to be joined.
   *     Returns null if one of the parts is null.
   */
  private static String joinAndEscapeUIDParts(String[] parts) {
    return TimelineReaderUtils.joinAndEscapeStrings(parts, UID_DELIMITER_CHAR,
        UID_ESCAPE_CHAR);
  }

  /**
   * Encodes UID depending on UID implementation.
   *
   * @param context Reader context.
   * @return UID represented as a string.
   */
  abstract String encodeUID(TimelineReaderContext context);

  /**
   * Decodes UID depending on UID implementation.
   *
   * @param uId UID to be decoded.
   * @return a {@link TimelineReaderContext} object if UID passed can be
   * decoded, null otherwise.
   * @throws Exception if any problem occurs while decoding.
   */
  abstract TimelineReaderContext decodeUID(String uId) throws Exception;
}
