from decimal import Decimal, getcontext
import logging
from typing import List, Tuple

# Decimal precision
getcontext().prec = 120

# Logging
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(message)s')
logger = logging.getLogger(__name__)

class PhiCompressionAdaptive:
    def __init__(self, phi: str = "1.6180339887498948482", epsilon: str = "1e-12", max_value: int = 256):
        self.PHI = Decimal(phi)
        self.EPSILON = Decimal(epsilon)
        self.MAX_VALUE = max_value
        self.SATURATE_SLOT = Decimal('9223372036854775')
        self.log_phi = self.PHI.ln()
        self.inverse_terms = [Decimal('105.0'), Decimal('99.9999999999'), Decimal('9.9999999999'), Decimal('4.2360679775'), Decimal('3.1415926535'), Decimal('1.6180339887')]

    def encode_sequence(self, sequence: List[int]) -> Decimal:
        if not sequence:
            return Decimal('0')
        if any(not (0 <= x < self.MAX_VALUE) for x in sequence):
            raise ValueError(f"Sequence elements must be in [0, {self.MAX_VALUE-1}]")
        sum_phi = sum(self.PHI ** Decimal(x) for x in sequence)
        exponent = sum_phi.ln() / self.log_phi
        return self.PHI ** exponent % self.SATURATE_SLOT

    def decode_sequence(self, encoded: Decimal, seq_length: int) -> List[int]:
        if encoded <= 0:
            return []
        sum_phi = self.PHI ** (encoded.ln() / self.log_phi)
        sequence = []
        current_sum = sum_phi
        for _ in range(seq_length):
            if current_sum <= 0:
                break
            b = int((current_sum.ln() / self.log_phi).to_integral_value(rounding="ROUND_FLOOR"))
            if b >= self.MAX_VALUE or b < 0:
                break
            sequence.append(b)
            current_sum -= self.PHI ** Decimal(b)
        return sequence

    def compute_padding(self, encoded: Decimal) -> Decimal:
        return sum(-(encoded.ln() / self.log_phi) / (a + 1) for a in self.inverse_terms)

    def zero_folding_error(self, encoded: Decimal) -> Decimal:
        padding = self.compute_padding(encoded)
        return abs(encoded + padding)

    def encode_multi_sequence(self, sequences: List[List[int]]) -> List[Decimal]:
        return [self.encode_sequence(seq) for seq in sequences]

    def fold_sequences(self, encoded_vector: List[Decimal]) -> Decimal:
        if not encoded_vector:
            return Decimal('0')
        sum_phi = sum(self.PHI ** e for e in encoded_vector)
        exponent = sum_phi.ln() / self.log_phi
        return self.PHI ** exponent % self.SATURATE_SLOT

    def decode_folded(self, folded: Decimal, seq_lengths: List[int], passes: int = 3) -> List[List[int]]:
        if folded <= 0:
            return [[] for _ in seq_lengths]
        residual = folded
        final_estimates = [Decimal('0')] * len(seq_lengths)
        for _ in range(passes):
            estimates = self._adaptive_gradient_descent(residual, seq_lengths)
            # Fold estimates back to compute new residual
            folded_estimate = self.fold_sequences([self.PHI ** e for e in estimates])
            residual = folded - folded_estimate
            final_estimates = [f + e for f, e in zip(final_estimates, estimates)]
        reconstructed = [self.decode_sequence(e, length) for e, length in zip(final_estimates, seq_lengths)]
        return reconstructed

    def _adaptive_gradient_descent(self, folded: Decimal, seq_lengths: List[int]) -> List[Decimal]:
        N = len(seq_lengths)
        max_encoded = ((Decimal(N) * (self.PHI ** (self.MAX_VALUE - 1))).ln() / self.log_phi)
        initial_guess = [((Decimal(max(length, 1)) * self.PHI ** (self.MAX_VALUE - 1)).ln() / self.log_phi)/Decimal(N) for length in seq_lengths]
        learning_rate = Decimal('0.05')
        max_iterations = 2000
        tolerance = Decimal('1e-10')
        estimates = initial_guess[:]
        best_estimates = estimates[:]
        best_error = Decimal('1e50')
        sum_phi = self.PHI ** (folded.ln() / self.log_phi)

        for _ in range(max_iterations):
            current_sum = sum(self.PHI ** e for e in estimates)
            error = current_sum - sum_phi
            abs_error = abs(error)
            if abs_error < best_error:
                best_error = abs_error
                best_estimates = estimates[:]
            if abs_error < tolerance:
                break
            gradients = [2 * error * (self.PHI ** e) * self.log_phi for e in estimates]
            old_estimates = estimates[:]
            for i in range(N):
                estimates[i] -= learning_rate * gradients[i]
                estimates[i] = max(Decimal('0'), min(estimates[i], max_encoded))
            new_sum = sum(self.PHI ** e for e in estimates)
            new_error = abs(new_sum - sum_phi)
            if new_error > abs_error:
                learning_rate *= Decimal('0.5')
                estimates = old_estimates
            else:
                learning_rate *= Decimal('1.05')
        return best_estimates

# Example usage
def main():
    compressor = PhiCompressionAdaptive()
    sequences = [[35,33,10],[97,98],[120,121,122]]
    encoded_vector = compressor.encode_multi_sequence(sequences)
    folded = compressor.fold_sequences(encoded_vector)
    seq_lengths = [len(seq) for seq in sequences]
    reconstructed = compressor.decode_folded(folded, seq_lengths, passes=3)
    logger.info(f"Folded: {folded}")
    logger.info(f"Reconstructed: {reconstructed}")

if __name__ == "__main__":
    main()
