"""
GRNexus Text Processing Module

This module provides text processing operations using the compiled C library.
Compatible with Windows (.dll), Linux (.so), and macOS (.dylib).
"""

import ctypes
import platform
import os
from typing import List, Optional, Tuple

# Detect operating system and load appropriate library
# Detect operating system and load appropriate library
def detect_library():
    script_dir = os.path.dirname(os.path.abspath(__file__))
    system = platform.system()
    
    # Determine library name and platform directory
    if system == 'Windows':
        lib_name = 'text_processing.dll'
        platform_dir = 'Windows'
    elif system == 'Darwin':  # macOS
        lib_name = 'text_processing.dylib'
        platform_dir = 'Mac'
    elif system == 'Linux':
        lib_name = 'text_processing.so'
        platform_dir = 'Linux'
    else:
        raise OSError(f"Sistema operativo no soportado: {system}")
    
    # Try multiple possible locations
    possible_paths = [
        # Installed package location (site-packages/grnexus/exports/...)
        os.path.join(script_dir, '..', 'exports', platform_dir, lib_name),
        # Development location (python/lib/../../exports/...)
        os.path.join(script_dir, '..', '..', 'exports', platform_dir, lib_name),
    ]
    
    for lib_path in possible_paths:
        normalized_path = os.path.normpath(lib_path)
        if os.path.exists(normalized_path):
            return normalized_path
    
    # If not found, raise an error with helpful message
    raise FileNotFoundError(
        f"No se pudo encontrar la biblioteca nativa {lib_name} para {system}. "
        f"Buscado en: {', '.join(possible_paths)}"
    )

# Load the shared library
_lib = ctypes.CDLL(detect_library())

# Define GRNexusData structure
class GRNexusData(ctypes.Structure):
    _fields_ = [
        ('data', ctypes.POINTER(ctypes.c_double)),
        ('type', ctypes.c_int),
        ('size', ctypes.c_size_t),
        ('stride', ctypes.c_size_t),
        ('dims', ctypes.c_size_t * 3)
    ]

# Define GRNexusVocabulary structure
class GRNexusVocabulary(ctypes.Structure):
    _fields_ = [
        ('tokens', ctypes.POINTER(ctypes.c_char_p)),
        ('indices', ctypes.POINTER(ctypes.c_size_t)),
        ('frequencies', ctypes.POINTER(ctypes.c_double)),
        ('idf_scores', ctypes.POINTER(ctypes.c_double)),
        ('vocab_size', ctypes.c_size_t),
        ('max_token_len', ctypes.c_size_t),
        ('total_tokens', ctypes.c_size_t),
        ('special_tokens', ctypes.c_char_p * 10),
        ('special_indices', ctypes.POINTER(ctypes.c_size_t))
    ]

# Define GRNexusEmbeddings structure
class GRNexusEmbeddings(ctypes.Structure):
    _fields_ = [
        ('embeddings', ctypes.POINTER(ctypes.c_double)),
        ('vocab_size', ctypes.c_size_t),
        ('embedding_dim', ctypes.c_size_t),
        ('norms', ctypes.POINTER(ctypes.c_double))
    ]

# Define GRNexusTFIDF structure
class GRNexusTFIDF(ctypes.Structure):
    _fields_ = [
        ('tfidf_matrix', ctypes.POINTER(ctypes.POINTER(ctypes.c_double))),
        ('num_docs', ctypes.c_size_t),
        ('vocab_size', ctypes.c_size_t)
    ]

# Define function signatures
_lib.create_vocabulary_advanced.restype = ctypes.POINTER(GRNexusVocabulary)
_lib.create_vocabulary_advanced.argtypes = [ctypes.POINTER(ctypes.c_char_p), ctypes.c_size_t, ctypes.c_size_t]

_lib.normalize_vocabulary_advanced.restype = ctypes.c_int
_lib.normalize_vocabulary_advanced.argtypes = [ctypes.c_char_p, ctypes.POINTER(GRNexusVocabulary), 
                                                ctypes.POINTER(GRNexusData), ctypes.c_size_t, ctypes.c_char_p]

_lib.denormalize_vocabulary.restype = ctypes.c_int
_lib.denormalize_vocabulary.argtypes = [ctypes.POINTER(GRNexusData), ctypes.POINTER(GRNexusVocabulary), 
                                         ctypes.POINTER(ctypes.c_char_p)]

_lib.vectorize_text_tfidf.restype = ctypes.c_int
_lib.vectorize_text_tfidf.argtypes = [ctypes.c_char_p, ctypes.POINTER(GRNexusVocabulary), 
                                       ctypes.POINTER(GRNexusData), ctypes.POINTER(GRNexusTFIDF)]

_lib.create_embeddings.restype = ctypes.POINTER(GRNexusEmbeddings)
_lib.create_embeddings.argtypes = [ctypes.POINTER(GRNexusVocabulary), ctypes.c_size_t]

_lib.find_similar_embeddings.restype = ctypes.c_int
_lib.find_similar_embeddings.argtypes = [ctypes.POINTER(GRNexusEmbeddings), ctypes.c_size_t,
                                          ctypes.POINTER(ctypes.c_double), ctypes.POINTER(ctypes.c_size_t),
                                          ctypes.c_size_t]

_lib.create_tfidf_model.restype = ctypes.POINTER(GRNexusTFIDF)
_lib.create_tfidf_model.argtypes = [ctypes.POINTER(GRNexusVocabulary), ctypes.POINTER(ctypes.c_char_p), ctypes.c_size_t]

_lib.find_similar_documents.restype = ctypes.c_int
_lib.find_similar_documents.argtypes = [ctypes.POINTER(GRNexusTFIDF), ctypes.c_size_t,
                                         ctypes.POINTER(ctypes.c_double), ctypes.POINTER(ctypes.c_size_t),
                                         ctypes.c_size_t]

_lib.free_vocabulary.restype = None
_lib.free_vocabulary.argtypes = [ctypes.POINTER(GRNexusVocabulary)]

_lib.free_embeddings.restype = None
_lib.free_embeddings.argtypes = [ctypes.POINTER(GRNexusEmbeddings)]

_lib.free_tfidf.restype = None
_lib.free_tfidf.argtypes = [ctypes.POINTER(GRNexusTFIDF)]

# Helper functions
def create_grnexus_data(size: int):
    """Create an empty GRNexusData structure."""
    buffer = (ctypes.c_double * size)()
    data = GRNexusData()
    data.data = ctypes.cast(buffer, ctypes.POINTER(ctypes.c_double))
    data.type = 1  # ARRAY
    data.size = size
    data.stride = 1
    data.dims[0] = size
    data.dims[1] = 0
    data.dims[2] = 0
    return data, buffer

# Main classes
class Vocabulary:
    """Vocabulary management for text processing"""
    
    def __init__(self, documents: List[str], max_vocab_size: int = 50000):
        """
        Create a vocabulary from documents.
        
        Args:
            documents: List of text documents
            max_vocab_size: Maximum vocabulary size
        """
        # Convert Python strings to C strings
        c_docs = (ctypes.c_char_p * len(documents))()
        for i, doc in enumerate(documents):
            c_docs[i] = doc.encode('utf-8')
        
        self._vocab_ptr = _lib.create_vocabulary_advanced(c_docs, len(documents), max_vocab_size)
        if not self._vocab_ptr:
            raise RuntimeError("Failed to create vocabulary")
    
    def __del__(self):
        """Free vocabulary resources"""
        if hasattr(self, '_vocab_ptr') and self._vocab_ptr:
            _lib.free_vocabulary(self._vocab_ptr)
    
    @property
    def size(self) -> int:
        """Get vocabulary size"""
        return self._vocab_ptr.contents.vocab_size
    
    @property
    def total_tokens(self) -> int:
        """Get total number of tokens processed"""
        return self._vocab_ptr.contents.total_tokens
    
    def get_tokens(self, max_tokens: int = 100) -> List[str]:
        """Get list of tokens from vocabulary"""
        tokens = []
        for i in range(min(max_tokens, self.size)):
            token = self._vocab_ptr.contents.tokens[i]
            if token:
                tokens.append(token.decode('utf-8'))
        return tokens
    
    def normalize_text(self, text: str, max_length: int = 512, strategy: str = "pad_right") -> List[float]:
        """
        Normalize text to token indices.
        
        Args:
            text: Input text
            max_length: Maximum sequence length
            strategy: Padding strategy
            
        Returns:
            List of token indices
        """
        output_data, output_buffer = create_grnexus_data(max_length)
        
        result = _lib.normalize_vocabulary_advanced(
            text.encode('utf-8'),
            self._vocab_ptr,
            ctypes.byref(output_data),
            max_length,
            strategy.encode('utf-8')
        )
        
        if result != 0:
            raise RuntimeError(f"Text normalization failed with code: {result}")
        
        return list(output_buffer[:max_length])
    
    def denormalize_indices(self, indices: List[float]) -> str:
        """
        Convert token indices back to text.
        
        Args:
            indices: List of token indices
            
        Returns:
            Reconstructed text
        """
        # Create input data
        input_data, input_buffer = create_grnexus_data(len(indices))
        for i, idx in enumerate(indices):
            input_buffer[i] = idx
        
        # Output text
        output_text = ctypes.c_char_p()
        
        result = _lib.denormalize_vocabulary(
            ctypes.byref(input_data),
            self._vocab_ptr,
            ctypes.byref(output_text)
        )
        
        if result != 0:
            raise RuntimeError(f"Denormalization failed with code: {result}")
        
        text = output_text.value.decode('utf-8') if output_text.value else ""
        # Note: C library is responsible for freeing the allocated string
        return text


class TextVectorizer:
    """TF-IDF text vectorization"""
    
    def __init__(self, vocabulary: Vocabulary):
        """
        Initialize vectorizer with vocabulary.
        
        Args:
            vocabulary: Vocabulary object
        """
        self.vocabulary = vocabulary
    
    def vectorize(self, text: str) -> List[float]:
        """
        Vectorize text using TF-IDF.
        
        Args:
            text: Input text
            
        Returns:
            TF-IDF vector
        """
        output_data, output_buffer = create_grnexus_data(self.vocabulary.size)
        
        result = _lib.vectorize_text_tfidf(
            text.encode('utf-8'),
            self.vocabulary._vocab_ptr,
            ctypes.byref(output_data),
            None  # tfidf_model parameter (optional)
        )
        
        if result != 0:
            raise RuntimeError(f"Vectorization failed with code: {result}")
        
        return list(output_buffer[:self.vocabulary.size])


class TextEmbeddings:
    """Word embeddings management"""
    
    def __init__(self, vocabulary: Vocabulary, embedding_dim: int = 100):
        """
        Create embeddings for vocabulary.
        
        Args:
            vocabulary: Vocabulary object
            embedding_dim: Dimension of embeddings
        """
        self.vocabulary = vocabulary
        self.embedding_dim = embedding_dim
        self._embeddings_ptr = _lib.create_embeddings(vocabulary._vocab_ptr, embedding_dim)
        
        if not self._embeddings_ptr:
            raise RuntimeError("Failed to create embeddings")
    
    def __del__(self):
        """Free embeddings resources"""
        if hasattr(self, '_embeddings_ptr') and self._embeddings_ptr:
            _lib.free_embeddings(self._embeddings_ptr)
    
    def find_similar(self, token_idx: int, top_k: int = 10) -> Tuple[List[int], List[float]]:
        """
        Find similar tokens based on embeddings.
        
        Args:
            token_idx: Index of token to find similar tokens for
            top_k: Number of similar tokens to return
            
        Returns:
            Tuple of (indices, similarities)
        """
        similarities = (ctypes.c_double * self.vocabulary.size)()
        similar_indices = (ctypes.c_size_t * top_k)()
        
        result = _lib.find_similar_embeddings(
            self._embeddings_ptr,
            token_idx,
            similarities,
            similar_indices,
            top_k
        )
        
        if result != 0:
            raise RuntimeError(f"Finding similar embeddings failed with code: {result}")
        
        return list(similar_indices[:top_k]), [similarities[i] for i in similar_indices[:top_k]]


class TFIDFModel:
    """TF-IDF model for document similarity"""
    
    def __init__(self, vocabulary: Vocabulary, documents: List[str]):
        """
        Create TF-IDF model from documents.
        
        Args:
            vocabulary: Vocabulary object
            documents: List of documents
        """
        self.vocabulary = vocabulary
        self.num_docs = len(documents)
        
        # Convert documents to C strings
        c_docs = (ctypes.c_char_p * len(documents))()
        for i, doc in enumerate(documents):
            c_docs[i] = doc.encode('utf-8')
        
        self._tfidf_ptr = _lib.create_tfidf_model(vocabulary._vocab_ptr, c_docs, len(documents))
        
        if not self._tfidf_ptr:
            raise RuntimeError("Failed to create TF-IDF model")
    
    def __del__(self):
        """Free TF-IDF resources"""
        if hasattr(self, '_tfidf_ptr') and self._tfidf_ptr:
            _lib.free_tfidf(self._tfidf_ptr)
    
    def find_similar_documents(self, doc_idx: int, top_k: int = 5) -> Tuple[List[int], List[float]]:
        """
        Find similar documents.
        
        Args:
            doc_idx: Index of document to find similar documents for
            top_k: Number of similar documents to return
            
        Returns:
            Tuple of (indices, similarities)
        """
        similarities = (ctypes.c_double * self.num_docs)()
        similar_docs = (ctypes.c_size_t * top_k)()
        
        result = _lib.find_similar_documents(
            self._tfidf_ptr,
            doc_idx,
            similarities,
            similar_docs,
            top_k
        )
        
        if result != 0:
            raise RuntimeError(f"Finding similar documents failed with code: {result}")
        
        return list(similar_docs[:top_k]), [similarities[i] for i in similar_docs[:top_k]]


# Legacy placeholder classes for compatibility
class TextProcessor:
    def process(self, text):
        raise NotImplementedError("Must implement text processing method")

class Tokenizer(TextProcessor):
    pass

class WordEmbedding(TextProcessor):
    pass

class TextNormalization(TextProcessor):
    pass
