package com.siimkinks.sqlitemagic.element;

import com.google.common.base.Joiner;
import com.google.common.base.Strings;
import com.siimkinks.sqlitemagic.Environment;
import com.siimkinks.sqlitemagic.annotation.Index;
import com.siimkinks.sqlitemagic.annotation.Table;

import java.util.ArrayList;
import java.util.List;

import javax.lang.model.element.Element;
import javax.lang.model.element.TypeElement;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import lombok.Getter;

import static com.siimkinks.sqlitemagic.element.TableElement.determineTableName;

public class IndexElement {
  private final Environment environment;
  @Getter
  private final Element indexElement;
  private final Index indexAnnotation;
  @Getter
  private final TypeElement tableTypeElement;
  @Getter
  private final boolean unique;
  @Getter
  private final boolean composite;
  @Nullable
  private List<String> indexedColumnNames = null;

  public IndexElement(Environment environment, Element indexElement) {
    this.environment = environment;
    this.indexElement = indexElement;
    this.indexAnnotation = indexElement.getAnnotation(Index.class);
    this.unique = indexAnnotation.unique();
    this.composite = indexElement instanceof TypeElement;
    this.tableTypeElement = getTableTypeElement(composite, indexElement);
  }

  @Nullable
  private static TypeElement getTableTypeElement(boolean composite, Element indexElement) {
    if (composite) {
      return (TypeElement) indexElement;
    } else {
      final Element enclosingElement = indexElement.getEnclosingElement();
      if (enclosingElement != null && enclosingElement instanceof TypeElement) {
        return (TypeElement) enclosingElement;
      }
    }
    return null;
  }

  public String getTableName() {
    final TypeElement tableTypeElement = this.tableTypeElement;
    final Table tableAnnotation = tableTypeElement.getAnnotation(Table.class);
    return determineTableName(tableTypeElement.getSimpleName().toString(), tableAnnotation.value());
  }

  public String getIndexName() {
    final String value = indexAnnotation.value();
    if (Strings.isNullOrEmpty(value)) {
      final String tableName = getTableName();
      final List<String> indexedColumns = getIndexedColumnNames();
      return "index_" + tableName + "_" + Joiner.on("_").join(indexedColumns);
    }
    return value;
  }

  @NonNull
  public List<String> getIndexedColumnNames() {
    if (indexedColumnNames != null) {
      return indexedColumnNames;
    }
    final ArrayList<String> columnNames = new ArrayList<>();
    final TableElement tableElement = getTableElement(environment, getTableName());
    if (tableElement != null) {
      if (composite) {
        final String indexName = indexAnnotation.value();
        for (ColumnElement columnElement : tableElement.getAllColumns()) {
          if (columnElement.getColumnAnnotation().belongsToIndex().equals(indexName)) {
            columnNames.add(columnElement.getColumnName());
          }
        }
      } else {
        final Element indexElement = this.indexElement;
        for (ColumnElement columnElement : tableElement.getAllColumns()) {
          if (columnElement.getColumnElement() == indexElement) {
            columnNames.add(columnElement.getColumnName());
            break;
          }
        }
      }
    }
    indexedColumnNames = columnNames;
    return columnNames;
  }

  @Nullable
  private static TableElement getTableElement(Environment environment, String tableName) {
    for (TableElement tableElement : environment.getAllTableElements()) {
      if (tableElement.getTableName().equals(tableName)) {
        return tableElement;
      }
    }
    return null;
  }

  public String indexSQL() {
    final StringBuilder sb = new StringBuilder("CREATE ");
    if (isUnique()) {
      sb.append("UNIQUE ");
    }
    sb.append("INDEX IF NOT EXISTS ")
        .append(getIndexName())
        .append(" ON ")
        .append(getTableName())
        .append(" (");
    Joiner.on(',').appendTo(sb, getIndexedColumnNames());
    sb.append(")");
    return sb.toString();
  }
}