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

import static com.knecon.fforesight.service.layoutparser.processor.model.text.RedTextPosition.HEIGHT_PADDING;

import java.awt.geom.AffineTransform;
import java.awt.geom.Rectangle2D;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.Objects;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

import org.apache.pdfbox.text.TextPosition;

import com.knecon.fforesight.service.layoutparser.processor.docstrum.model.TextBoundingBox;

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

@Slf4j
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@SuppressWarnings("pmd")
public class Word extends TextBoundingBox implements CharSequence {

    public static final String STANDARD = "standard";
    public static final String BOLD_ITALIC = "bold, italic";
    public static final String BOLD = "bold";
    public static final String ITALIC = "italic";
    public static final Pattern FONT_CLEANER = Pattern.compile(",bold|,italic");

    private int page;

    @Builder.Default
    private List<RedTextPosition> textPositions = new ArrayList<>();

    private boolean isParagraphStart;
    private boolean strikethrough;
    private boolean underline;

    private Integer hashcodeCache;


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

        this.textPositions = textPositions.stream()
                .map(RedTextPosition::fromTextPosition)
                .collect(Collectors.toList());
        this.page = pageNumber;
        this.isParagraphStart = isParagraphStart;
        calculateBBoxAndHashcode();
    }


    private void calculateBBoxAndHashcode() {

        setToBBoxOfComponents(getTextPositions());
        hashcodeCache = null;
    }


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

        this.textPositions = textPositions;
        this.page = page;
        calculateBBoxAndHashcode();
    }


    @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 Word subSequence(int start, int end) {

        var textPositionSequence = new Word();
        textPositionSequence.textPositions = textPositions.subList(start, end);
        textPositionSequence.page = page;
        textPositionSequence.dir = dir;
        textPositionSequence.setToBBoxOfComponents(getTextPositions());
        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(Word word, RedTextPosition textPosition) {

        this.textPositions.add(textPosition);
        this.page = word.getPage();
        calculateBBoxAndHashcode();
    }


    public void add(TextPosition textPosition) {

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


    public double getTextHeightNoPadding() {

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


    public double getTextHeight() {

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


    public String getFont() {

        if (textPositions.get(0).getFontName() == null) {
            return "none";
        }

        return FONT_CLEANER.matcher(textPositions.get(0).getFontName().toLowerCase(Locale.ROOT)).replaceAll("");
    }


    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();
    }


    public boolean equals(final Object o) {
        // auto-generated with lombok
        if (o == this) {
            return true;
        }
        if (!(o instanceof Word other)) {
            return false;
        }
        if (!other.canEqual((Object) this)) {
            return false;
        }
        if (!super.equals(o)) {
            return false;
        }
        if (this.getPage() != other.getPage()) {
            return false;
        }
        final Object this$textPositions = this.getTextPositions();
        final Object other$textPositions = other.getTextPositions();
        if (!Objects.equals(this$textPositions, other$textPositions)) {
            return false;
        }
        return Objects.equals(this.getHashcodeCache(), other.getHashcodeCache());
    }


    protected boolean canEqual(final Object other) {return other instanceof Word;}


    public int hashCode() {

        if (hashcodeCache == null) {
            hashcodeCache = hashcodeCalculation();
        }

        return hashcodeCache;
    }


    private int hashcodeCalculation() {

        final int PRIME = 59;
        int result = super.hashCode();
        result = result * PRIME + this.getPage();
        final Object $textPositions = this.getTextPositions();
        result = result * PRIME + ($textPositions == null ? 43 : $textPositions.hashCode());
        return result;
    }


    public void transform(AffineTransform rotateInstance) {

        for (RedTextPosition textPosition : getTextPositions()) {
            Rectangle2D exactDirAdjCoordinates = rotateInstance.createTransformedShape(textPosition.getBBoxDirAdj()).getBounds2D();
            textPosition.setBBoxDirAdj(exactDirAdjCoordinates);
        }
        calculateBBoxAndHashcode();
    }

}

