<?php

if ( !defined( 'ABSPATH' ) ) {

    exit;
}

if ( !class_exists( 'Pricely_Totals_Util' ) ) {

    class Pricely_Totals_Util {

        private static $instance;

        public static function get_instance(): self {

            if ( !self::$instance ) {

                self::$instance = new self();
            }

            return self::$instance;
        }

        public function round( $value, int $precision = -1, int $mode = PHP_ROUND_HALF_UP ): float {

            if ( $precision < 0 ) {

                $precision = $this->get_decimals();
            }

            if ( !is_numeric( $value ) ) {

                $value = floatval( $value );
            }

            return round( $value, $precision, $mode );
        }

        public function get_decimals() {

            return absint( get_option( 'woocommerce_price_num_decimals', 2 ) );
        }

        public function split_total( $total, $in_qty, $default = false ) {

            if ( $total <= 0 ) {

                return $default;
            }

            $qty = $this->get_max_qty( $total, $in_qty );

            if ( $qty <= 0 ) {

                return $default;
            }

            //Get total per item unit
            $total_per_item = $this->round( $total / $qty );

            //Convert per item unit to array
            $totals = array();

            for ( $i = 0; $i < $qty; $i++ ) {

                $totals[] = $total_per_item;
            }

            ////Adjust totals discrepancy
            $totals = $this->adjust_remainder( $totals, $total );

            return $totals;
        }

        public function create_total_per_unit( $unit_total, $args, $default = false ) {

            if ( $unit_total <= 0 ) {

                return $default;
            }

            if ( !isset( $args[ 'items' ] ) || !count( $args[ 'items' ] ) ) {

                return $default;
            }

            //Prepare item totals
            $totals = array();

            foreach ( $args[ 'items' ] as $key => $item ) {

                $qty = 1;

                if ( isset( $item[ 'qty' ] ) ) {

                    $qty = $item[ 'qty' ];
                }

                $line_item_total = $unit_total * $qty; // calculate line total for splitting

                $totals[ $key ][ 'qty' ] = $qty;
                $totals[ $key ][ 'totals' ] = $this->split_total( $line_item_total, $qty );
            }

            return $totals;
        }

        public function split_total_for_items( $total, $args, $default = false ) {

            if ( $total <= 0 ) {

                return $default;
            }

            if ( !isset( $args[ 'items' ] ) || !count( $args[ 'items' ] ) ) {

                return $default;
            }

            $items = $args[ 'items' ];

            //Convert total to totals per item unit based on qty
            if ( 'qty' == $args[ 'mode' ] ) {

                $totals = $this->get_items_totals_by_qty( $total, $items );
            } elseif ( 'total' == $args[ 'mode' ] ) {

                $totals = $this->get_items_totals( $total, $items );
            } else {

                return $default;
            }

            return $totals;
        }

        public function adjust_remainder( $totals, $total ) {

            $total_calc = $this->round( array_sum( $totals ) );

            if ( $this->round( $total ) === $total_calc ) {

                return $totals;
            }

            if ( $this->round( $total ) > $total_calc ) {

                $totals = $this->add_remainder( $totals, $total_calc, $total );
            }

            if ( $this->round( $total ) < $total_calc ) {

                $totals = $this->remove_remainder( $totals, $total_calc, $total );
            }

            return $totals;
        }

        private function get_items_totals( $total, $items ) {

            //Get all item totals
            $items_total = $this->merge_items_totals( $items );

            $line_item_totals = array();

            foreach ( $items as $key => $item ) {

                $line_item_totals[ $key ] = $this->round( ($item[ 'total' ] / $items_total) * $total );
            }

            //Adjust totals discrepancy
            $line_item_totals = $this->adjust_remainder( $line_item_totals, $total );

            //Prepare item totals
            $totals = array();

            foreach ( $line_item_totals as $key => $line_item_total ) {

                if ( !isset( $items[ $key ] ) ) {

                    continue;
                }

                $qty = 1;

                if ( isset( $items[ $key ][ 'qty' ] ) ) {

                    $qty = $this->get_max_qty( $line_item_total, $items[ $key ][ 'qty' ] );
                }

                $totals[ $key ][ 'qty' ] = $qty;
                $totals[ $key ][ 'totals' ] = $this->split_total( $line_item_total, $qty );
            }

            return $totals;
        }

        private function get_items_totals_by_qty( $total, $items ) {

            //Get all item qty
            $items_qty = $this->merge_items_qty( $items );

            //Get totals per item unit as array
            $qty_based_totals = $this->split_total( $total, $items_qty );

            //Prepare item totals
            $totals = array();

            $keys = array_keys( $items );

            $next_key_index = 0;

            $next_key = $keys[ $next_key_index ];

            foreach ( $qty_based_totals as $qty_based_total ) {

                if ( !isset( $totals[ $next_key ] ) ) {

                    $totals[ $next_key ][ 'qty' ] = 0;
                    $totals[ $next_key ][ 'totals' ] = array();
                }

                $totals[ $next_key ][ 'qty' ] += 1;
                $totals[ $next_key ][ 'totals' ][] = $qty_based_total;

                $totals_count = $totals[ $next_key ][ 'qty' ];
                $totals_max = $items[ $next_key ][ 'qty' ];


                if ( $totals_count >= $totals_max ) {

                    $next_key_index++;
                }

                if ( isset( $keys[ $next_key_index ] ) ) {

                    $next_key = $keys[ $next_key_index ];
                }
            }

            return $totals;
        }

        private function merge_items_qty( $items ) {

            $qty = 0;

            foreach ( $items as $item ) {

                $qty += $item[ 'qty' ];
            }

            return $qty;
        }

        private function merge_items_totals( $items ) {

            $total = 0;

            foreach ( $items as $item ) {

                $total += $item[ 'total' ];
            }

            return $total;
        }

        private function add_remainder( $totals, $total_calc, $total ) {

            $keys = array_keys( $totals );
            $keys_count = count( $keys );

            $cent = $this->remove_precision( 1 );

            $remainder = $this->get_remainder( $total, $total_calc );

            if ( $remainder <= 0 ) {

                return $totals;
            }

            $cent_count = ( int ) ($remainder / $cent);

            for ( $key_index = 0; ($key_index < $keys_count && $cent_count > 0 ); $key_index++ ) {

                if ( !isset( $keys[ $key_index ] ) ) {

                    break;
                }

                $key = $keys[ $key_index ];

                $totals[ $key ] += $cent;

                $cent_count--;
            }

            return $totals;
        }

        private function remove_remainder( $totals, $total_calc, $total ) {

            $keys = array_keys( $totals );
            $keys_count = count( $keys );

            $cent = $this->remove_precision( 1 );

            $remainder = $this->get_remainder( $total_calc, $total );

            if ( $remainder <= 0 ) {

                return $totals;
            }

            $cent_count = ( int ) ($remainder / $cent);

            for ( $key_index = ($keys_count - 1); ($key_index >= 0 && $cent_count > 0 ); $key_index-- ) {

                if ( !isset( $keys[ $key_index ] ) ) {

                    break;
                }

                $key = $keys[ $key_index ];

                $totals[ $key ] -= $cent;

                $cent_count--;
            }

            return $totals;
        }

        private function get_remainder( $higher, $lower ) {

            $cent = $this->remove_precision( 1 );

            $remainder = 0;
            $new_lower = $lower;

            while ( $this->round( $higher ) > $this->round( $new_lower ) ) {

                $remainder += $cent;

                $new_lower += $cent;
            }

            return $remainder;
        }

        private function get_max_qty( $total, $qty ) {

            if ( $qty <= 0 ) {

                return $qty;
            }

            $cent = $this->remove_precision( 1 );

            $max_qty = ( int ) ($total / $cent);

            if ( $qty > $max_qty ) {

                return $max_qty;
            }

            return $qty;
        }

        private function remove_precision( $value ) {

            $cent = pow( 10, $this->get_decimals() );

            return $value / $cent;
        }

    }

}

