package io.delphiplatform.api.util;

import com.google.common.base.CaseFormat;

import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.domain.Sort.Direction;

import java.io.Serializable;
import java.util.Objects;

import io.delphiplatform.api.v3.constant.ApplicationConstants;
import io.delphiplatform.api.v3.model.SortOrder;
import io.delphiplatform.api.v3.view.util.Params;

public class OffsetBasedPageRequest implements Pageable, Serializable {

    private static final long serialVersionUID = -25822477129613575L;
    private final Sort sort;
    private final int limit;
    private final int offset;

    private OffsetBasedPageRequest(int offset, int limit, Sort sort) {
        this.limit = limit;
        this.offset = offset;
        this.sort = sort == null ? Sort.unsorted() : sort;
    }

    public static OffsetBasedPageRequest ofParamsToCamelCase(Params params) {
        return ofParamsToCamelCase(params.getOffset(), params.getLimit(), params.getSortOrder(), params.getSortBy());
    }

    public static OffsetBasedPageRequest ofParamsToCamelCase(Integer inputOffset, Integer inputLimit,
        SortOrder inputSortOrder,
        String inputSortBy) {
        String sortBy = underscoreToCamelCase(inputSortBy);

        return ofParamsKeepCase(inputOffset, inputLimit, inputSortOrder, sortBy);
    }

    public static OffsetBasedPageRequest ofParamsKeepCase(Params params) {
        return ofParamsKeepCase(params.getOffset(), params.getLimit(), params.getSortOrder(), params.getSortBy());
    }

    public static OffsetBasedPageRequest ofParamsKeepCase(
        Integer inputOffset,
        Integer inputLimit,
        SortOrder inputSortOrder,
        String sortBy) {
        int offset = inputOffset == null ? 0 : inputOffset;
        int limit = inputLimit == null || inputLimit < 1 ? ApplicationConstants.DEFAULT_PAGE_SIZE
            : inputLimit;
        Direction direction = SortOrder.DESC == inputSortOrder ? Direction.DESC : Direction.ASC;
        return new OffsetBasedPageRequest(offset, limit, sortBy == null ? Sort.unsorted() : Sort.by(direction, sortBy));
    }

    public static OffsetBasedPageRequest ofParamsKeepCase(Integer inputOffset, Integer inputLimit) {
        int offset = inputOffset == null ? 0 : inputOffset;
        int limit = inputLimit == null || inputLimit < 1 ? ApplicationConstants.DEFAULT_PAGE_SIZE : inputLimit;
        return new OffsetBasedPageRequest(offset, limit, null);
    }

    private static String underscoreToCamelCase(String sortBy) {
        if (CollectionUtils.isEmpty(sortBy)) {
            return null;
        }
        return CaseFormat.UPPER_UNDERSCORE.to(CaseFormat.LOWER_CAMEL, sortBy);
    }

    @Override
    public int getPageNumber() {
        return offset / limit;
    }

    @Override
    public int getPageSize() {
        return limit;
    }

    @Override
    public long getOffset() {
        return offset;
    }

    @Override
    public Sort getSort() {
        return sort;
    }

    @Override
    public Pageable next() {
        return new OffsetBasedPageRequest((int) (getOffset() + getPageSize()), getPageSize(), getSort());
    }

    public OffsetBasedPageRequest previous() {
        return hasPrevious() ? new OffsetBasedPageRequest((int) (getOffset() - getPageSize()), getPageSize(), getSort())
            : this;
    }


    @Override
    public Pageable previousOrFirst() {
        return hasPrevious() ? previous() : first();
    }

    @Override
    public Pageable first() {
        return new OffsetBasedPageRequest(0, getPageSize(), getSort());
    }

    @Override
    public Pageable withPage(int pageNumber) {
        int newOffset = pageNumber * limit;
        return new OffsetBasedPageRequest(newOffset, limit, sort);
    }

    @Override
    public boolean hasPrevious() {
        return offset > limit;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) {
            return true;
        }
        if (o == null || getClass() != o.getClass()) {
            return false;
        }
        OffsetBasedPageRequest that = (OffsetBasedPageRequest) o;
        return limit == that.limit &&
            offset == that.offset &&
            Objects.equals(sort, that.sort);
    }

    @Override
    public int hashCode() {
        return Objects.hash(limit, offset, sort);
    }

    @Override
    public String toString() {
        return "OffsetBasedPageRequest{" +
            "limit=" + limit +
            ", offset=" + offset +
            ", sort=" + sort +
            '}';
    }
}
