package com.knecon.fforesight.service.layoutparser.processor.model.text;

import java.awt.geom.AffineTransform;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.stream.Collectors;

import org.apache.pdfbox.text.TextPosition;

import com.iqser.red.service.persistence.service.v1.api.shared.model.redactionlog.Point;
import com.iqser.red.service.persistence.service.v1.api.shared.model.redactionlog.Rectangle;
import com.knecon.fforesight.service.layoutparser.processor.utils.RectangleTransformations;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;

@Slf4j
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@EqualsAndHashCode(onlyExplicitlyIncluded = true)
public class TextPositionSequence implements CharSequence {

    public static final int HEIGHT_PADDING = 2;

    @EqualsAndHashCode.Include
    private int page;

    @EqualsAndHashCode.Include
    private List<RedTextPosition> textPositions = new ArrayList<>();

    @EqualsAndHashCode.Include
    private TextDirection dir;
    private int rotation;
    private float pageHeight;
    private float pageWidth;
    private boolean isParagraphStart;
    private boolean strikethrough;
    private boolean underline;


    public TextPositionSequence(List<TextPosition> textPositions, int pageNumber, boolean isParagraphStart) {

        this.textPositions = textPositions.stream()
                .map(RedTextPosition::fromTextPosition)
                .collect(Collectors.toList());
        this.page = pageNumber;
        this.dir = TextDirection.fromDegrees(textPositions.get(0).getDir());
        this.rotation = textPositions.get(0).getRotation();
        this.pageHeight = textPositions.get(0).getPageHeight();
        this.pageWidth = textPositions.get(0).getPageWidth();
        this.isParagraphStart = isParagraphStart;


    }


    public TextPositionSequence(List<RedTextPosition> textPositions, int page) {

        this.textPositions = textPositions;
        this.page = page;
        this.dir = TextDirection.fromDegrees(textPositions.get(0).getDir());
        this.rotation = textPositions.get(0).getRotation();
        this.pageHeight = textPositions.get(0).getPageHeight();
        this.pageWidth = textPositions.get(0).getPageWidth();
    }


    @Override
    public int length() {

        return textPositions.size();
    }


    @Override
    public char charAt(int index) {

        RedTextPosition textPosition = textPositionAt(index);
        String text = textPosition.getUnicode();
        return text.charAt(0);
    }


    public char charAt(int index, boolean caseInSensitive) {

        RedTextPosition textPosition = textPositionAt(index);
        String text = textPosition.getUnicode();
        return caseInSensitive ? text.toLowerCase(Locale.ROOT).charAt(0) : text.charAt(0);
    }


    @Override
    public TextPositionSequence subSequence(int start, int end) {

        var textPositionSequence = new TextPositionSequence();
        textPositionSequence.textPositions = textPositions.subList(start, end);
        textPositionSequence.page = page;
        textPositionSequence.dir = dir;
        textPositionSequence.rotation = rotation;
        textPositionSequence.pageHeight = pageHeight;
        textPositionSequence.pageWidth = pageWidth;

        return textPositionSequence;
    }


    @Override
    public String toString() {

        StringBuilder builder = new StringBuilder(length());
        for (int i = 0; i < length(); i++) {
            builder.append(charAt(i));
        }
        return builder.toString();
    }


    public RedTextPosition textPositionAt(int index) {

        return textPositions.get(index);
    }


    public void add(TextPositionSequence textPositionSequence, RedTextPosition textPosition) {

        this.textPositions.add(textPosition);
        this.page = textPositionSequence.getPage();
        this.dir = textPositionSequence.getDir();
        this.rotation = textPositionSequence.getRotation();
        this.pageHeight = textPositionSequence.getPageHeight();
        this.pageWidth = textPositionSequence.getPageWidth();
    }


    public void add(TextPosition textPosition) {

        this.textPositions.add(RedTextPosition.fromTextPosition(textPosition));

        this.dir = TextDirection.fromDegrees(textPositions.get(0).getDir());
        this.rotation = textPositions.get(0).getRotation();
        this.pageHeight = textPositions.get(0).getPageHeight();
        this.pageWidth = textPositions.get(0).getPageWidth();

    }


    /**
     * This value is adjusted so that 0,0 is upper left and it is adjusted based on the text direction.
     * This method ignores the page rotation but takes the text rotation and adjusts the coordinates to awt.
     *
     * @return the text direction adjusted minX value
     */

    public float getMinXDirAdj() {

        return textPositions.get(0).getXDirAdj();

    }


    /**
     * This value is adjusted so that 0,0 is upper left and it is adjusted based on the text direction.
     * This method ignores the page rotation but takes the text rotation and adjusts the coordinates to awt.
     *
     * @return the text direction adjusted maxX value
     */

    public float getMaxXDirAdj() {

        return textPositions.get(textPositions.size() - 1).getXDirAdj() + textPositions.get(textPositions.size() - 1).getWidthDirAdj() + HEIGHT_PADDING;

    }


    /**
     * This value is adjusted so that 0,0 is upper left and it is adjusted based on the text direction.
     * This method ignores the page rotation but takes the text rotation and adjusts the coordinates to awt.
     *
     * @return the text direction adjusted minY value. The upper border of the bounding box of the word.
     */

    public float getMinYDirAdj() {

        return textPositions.get(0).getYDirAdj() - getTextHeight();

    }


    /**
     * This value is adjusted so that 0,0 is upper left and it is adjusted based on the text direction.
     * This method ignores the page rotation but takes the text rotation and adjusts the coordinates to awt.
     *
     * @return the text direction adjusted maxY value. The lower border of the bounding box of the word.
     */

    public float getMaxYDirAdj() {

        return textPositions.get(0).getYDirAdj();

    }


    public float getTextHeightNoPadding() {

        return textPositions.get(0).getHeightDir();
    }


    public float getTextHeight() {

        return textPositions.get(0).getHeightDir() + HEIGHT_PADDING;
    }


    public float getHeight() {

        return getMaxYDirAdj() - getMinYDirAdj();
    }


    public float getWidth() {

        return getMaxXDirAdj() - getMinXDirAdj();
    }


    public String getFont() {

        if (textPositions.get(0).getFontName() == null) {
            return "none";
        }
        return textPositions.get(0).getFontName().toLowerCase(Locale.ROOT).replaceAll(",bold", "").replaceAll(",italic", "");
    }


    public String getFontStyle() {

        if (textPositions.get(0).getFontName() == null) {
            return "standard";
        }
        String lowercaseFontName = textPositions.get(0).getFontName().toLowerCase(Locale.ROOT);

        if (lowercaseFontName.contains("bold") && lowercaseFontName.contains("italic")) {
            return "bold, italic";
        } else if (lowercaseFontName.contains("bold")) {
            return "bold";
        } else if (lowercaseFontName.contains("italic")) {
            return "italic";
        } else {
            return "standard";
        }
    }


    public float getFontSize() {

        return textPositions.get(0).getFontSizeInPt();
    }


    public float getSpaceWidth() {

        return textPositions.get(0).getWidthOfSpace();
    }


    /**
     * This returns the bounding box of the word in Pdf Coordinate System where {0,0} rotated with the page rotation.
     * 0 -> LowerLeft
     * 90 -> UpperLeft
     * 180 -> UpperRight
     * 270 -> LowerRight
     *
     * @return bounding box of the word in Pdf Coordinate System
     */

    @SneakyThrows
    public Rectangle getRectangle() {

        log.debug("Page: '{}', Word: '{}', Rotation: '{}', textRotation {}", page, this, rotation, dir);

        float textHeight = getTextHeight();

        RedTextPosition firstTextPos = textPositions.get(0);
        RedTextPosition lastTextPos = textPositions.get(textPositions.size() - 1);

        Point2D bottomLeft = new Point2D.Double(firstTextPos.getXDirAdj(), firstTextPos.getYDirAdj() - HEIGHT_PADDING);
        Point2D topRight = new Point2D.Double(lastTextPos.getXDirAdj() + lastTextPos.getWidthDirAdj(), lastTextPos.getYDirAdj() + textHeight + HEIGHT_PADDING);

        AffineTransform transform = new AffineTransform();
        if (dir == TextDirection.ZERO || dir == TextDirection.HALF_CIRCLE) {
            transform.rotate(dir.getRadians(), pageWidth / 2f, pageHeight / 2f);
            transform.translate(0f, pageHeight + textHeight);
            transform.scale(1., -1.);
        } else if (dir == TextDirection.QUARTER_CIRCLE) {
            transform.rotate(dir.getRadians(), pageWidth / 2f, pageWidth / 2f);
            transform.translate(0f, pageWidth + textHeight);
            transform.scale(1., -1.);
        } else {
            transform.rotate(dir.getRadians(), pageHeight / 2f, pageHeight / 2f);
            transform.translate(0f, pageWidth + textHeight);
            transform.scale(1., -1.);
        }

        bottomLeft = transform.transform(bottomLeft, null);
        topRight = transform.transform(topRight, null);

        return new Rectangle( //
                              new Point((float) bottomLeft.getX(), (float) bottomLeft.getY()),
                              (float) (topRight.getX() - bottomLeft.getX()),
                              (float) (topRight.getY() - bottomLeft.getY()),
                              page);
    }


    public Rectangle2D getBoundingBox() {

        return getTextPositions().stream()
                .map(RedTextPosition::getInitialUserSpacePosition)
                .collect(RectangleTransformations.collectBBox());
    }

}

