/**
 * FilterWorker - Web Worker for parallel filter processing
 * 
 * Handles CPU-intensive pixel processing off the main thread
 * Implements single-pass filter engine with optimized algorithms
 * 
 * Conditional Filter Processing Optimizations (Task 4.4):
 * - Analyzes filter requirements before processing to skip inactive filters
 * - Early exit when no filters are active (returns original image data)
 * - Skips color balance calculations for luminance ranges with no adjustments
 * - Optimizes color space conversions (luminance, HSL) to only compute when needed
 * - Batches similar operations together (exposure/offset/gamma, color operations, saturation)
 * - Pre-calculates lookup tables only for active filters
 * - Reduces unnecessary calculations in vibrance/saturation by computing average once
 * 
 * Requirements: 3.1, 3.3, 1.1
 */

// Message protocol types
const MessageTypes = {
  PROCESS_FILTERS: 'PROCESS_FILTERS',
  FILTER_COMPLETE: 'FILTER_COMPLETE',
  ERROR: 'ERROR',
  INITIALIZE: 'INITIALIZE',
  INITIALIZED: 'INITIALIZED'
};

// Worker state
let isInitialized = false;
let filterEngine = null;

/**
 * Initialize the worker
 */
function initialize() {
  try {
    // FilterEngine will be defined below
    filterEngine = new SinglePassFilterEngine();
    isInitialized = true;
    
    self.postMessage({
      type: MessageTypes.INITIALIZED,
      timestamp: Date.now()
    });
    
    console.log('[FilterWorker] Initialized successfully');
  } catch (error) {
    self.postMessage({
      type: MessageTypes.ERROR,
      error: error.message,
      timestamp: Date.now()
    });
  }
}

/**
 * Process filter request
 * @param {Object} data - Message data containing imageData and adjustments
 */
function processFilters(data) {
  const startTime = performance.now();
  
  try {
    if (!isInitialized || !filterEngine) {
      throw new Error('Worker not initialized');
    }
    
    const { id, imageData, adjustments, previewMode } = data;
    
    if (!imageData || !imageData.data) {
      throw new Error('Invalid imageData');
    }
    
    // Process the image with all filters in a single pass
    const processedImageData = filterEngine.processPixels(imageData, adjustments);
    
    const processingTime = performance.now() - startTime;
    
    // Send result back to main thread
    self.postMessage({
      type: MessageTypes.FILTER_COMPLETE,
      id: id,
      imageData: processedImageData,
      processingTime: processingTime,
      timestamp: Date.now()
    }, [processedImageData.data.buffer]); // Transfer ownership for performance
    
  } catch (error) {
    const processingTime = performance.now() - startTime;
    
    self.postMessage({
      type: MessageTypes.ERROR,
      id: data.id,
      error: error.message,
      stack: error.stack,
      processingTime: processingTime,
      timestamp: Date.now()
    });
  }
}

/**
 * Message handler
 */
self.onmessage = function(event) {
  const { type, data } = event.data;
  
  switch (type) {
    case MessageTypes.INITIALIZE:
      initialize();
      break;
      
    case MessageTypes.PROCESS_FILTERS:
      processFilters(data);
      break;
      
    default:
      console.warn('[FilterWorker] Unknown message type:', type);
  }
};

/**
 * Error handler
 */
self.onerror = function(error) {
  console.error('[FilterWorker] Unhandled error:', error);
  
  self.postMessage({
    type: MessageTypes.ERROR,
    error: error.message,
    filename: error.filename,
    lineno: error.lineno,
    colno: error.colno,
    timestamp: Date.now()
  });
};

/**
 * SinglePassFilterEngine - Optimized filter processing in a single loop
 * 
 * Processes all filters in one iteration over the pixel array
 * Requirements: 3.3, 1.1
 */
class SinglePassFilterEngine {
  constructor() {
    // Lookup tables for optimized calculations
    this.gammaLUT = null;
    this.exposureLUT = null;
    this.currentGamma = null;
    this.currentExposure = null;
    
    // HSL conversion cache
    this.hslCache = new Map();
    this.maxCacheSize = 1000;
    
    console.log('[SinglePassFilterEngine] Created');
  }
  
  /**
   * Initialize or update gamma correction lookup table
   * Requirement 1.1: Pre-calculate gamma correction lookup tables
   * 
   * @param {number} gamma - Gamma value
   */
  _initGammaLUT(gamma) {
    if (this.currentGamma === gamma && this.gammaLUT) {
      return; // Already initialized for this gamma value
    }
    
    this.gammaLUT = new Uint8Array(256);
    const invGamma = 1 / gamma;
    
    for (let i = 0; i < 256; i++) {
      this.gammaLUT[i] = Math.round(255 * Math.pow(i / 255, invGamma));
    }
    
    this.currentGamma = gamma;
  }
  
  /**
   * Initialize or update exposure lookup table
   * Requirement 1.1: Create exposure factor lookup tables
   * 
   * @param {number} exposure - Exposure value
   */
  _initExposureLUT(exposure) {
    if (this.currentExposure === exposure && this.exposureLUT) {
      return; // Already initialized for this exposure value
    }
    
    this.exposureLUT = new Uint16Array(256);
    const exposureFactor = Math.pow(2, exposure);
    
    for (let i = 0; i < 256; i++) {
      // Store as 16-bit to handle values > 255, will be clamped later
      this.exposureLUT[i] = Math.round(i * exposureFactor);
    }
    
    this.currentExposure = exposure;
  }
  
  /**
   * Convert RGB to HSL with caching
   * Requirement 1.1: Implement color space conversion caching
   * 
   * @param {number} r - Red channel value
   * @param {number} g - Green channel value
   * @param {number} b - Blue channel value
   * @returns {Object} HSL values { h, s, l }
   */
  _rgbToHSLCached(r, g, b) {
    // Create cache key from RGB values
    const key = (r << 16) | (g << 8) | b;
    
    // Check cache
    if (this.hslCache.has(key)) {
      return this.hslCache.get(key);
    }
    
    // Calculate HSL
    const max = Math.max(r, g, b);
    const min = Math.min(r, g, b);
    const l = (max + min) / 2;
    let h = 0;
    let s = 0;
    
    if (max !== min) {
      const d = max - min;
      s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
      
      if (max === r) {
        h = ((g - b) / d + (g < b ? 6 : 0)) / 6;
      } else if (max === g) {
        h = ((b - r) / d + 2) / 6;
      } else {
        h = ((r - g) / d + 4) / 6;
      }
    }
    
    h = h * 360; // Convert to degrees
    
    const result = { h, s, l };
    
    // Add to cache with size limit
    if (this.hslCache.size >= this.maxCacheSize) {
      // Remove oldest entry (first key)
      const firstKey = this.hslCache.keys().next().value;
      this.hslCache.delete(firstKey);
    }
    
    this.hslCache.set(key, result);
    
    return result;
  }
  
  /**
   * Clear HSL cache
   */
  _clearHSLCache() {
    this.hslCache.clear();
  }
  
  /**
   * Check if color balance has any active adjustments
   * Requirement 1.1: Skip filters with default values
   * @private
   */
  _hasColorBalanceAdjustments(adjustments) {
    return adjustments.shadowsCyan !== 0 || adjustments.shadowsMagenta !== 0 || adjustments.shadowsYellow !== 0 ||
           adjustments.midtonesCyan !== 0 || adjustments.midtonesMagenta !== 0 || adjustments.midtonesYellow !== 0 ||
           adjustments.highlightsCyan !== 0 || adjustments.highlightsMagenta !== 0 || adjustments.highlightsYellow !== 0;
  }
  
  /**
   * Check if any basic adjustments are active
   * Requirement 1.1: Skip filters with default values
   * @private
   */
  _hasBasicAdjustments(adjustments) {
    return adjustments.brightness !== 0 || adjustments.contrast !== 0 || 
           adjustments.saturation !== 0 || adjustments.hue !== 0 || 
           adjustments.lightness !== 0;
  }
  
  /**
   * Check if temperature/tint adjustments are active
   * Requirement 1.1: Skip filters with default values
   * @private
   */
  _hasTemperatureTintAdjustments(adjustments) {
    return adjustments.temperature !== 0 || adjustments.tint !== 0;
  }
  
  /**
   * Determine which filters need processing
   * Requirement 1.1: Add checks to skip filters with default values
   * @private
   */
  _analyzeFilterRequirements(adjustments) {
    return {
      needsExposure: adjustments.exposure !== undefined && adjustments.exposure !== 0,
      needsOffset: adjustments.offset !== undefined && adjustments.offset !== 0,
      needsGamma: adjustments.gammaCorrection !== undefined && adjustments.gammaCorrection !== 1.0,
      needsColorBalance: this._hasColorBalanceAdjustments(adjustments),
      needsBW: adjustments.bwEnabled === true,
      needsVibrance: (adjustments.vibrance !== undefined && adjustments.vibrance !== 0) || 
                     (adjustments.saturationVibrance !== undefined && adjustments.saturationVibrance !== 0),
      needsBasic: this._hasBasicAdjustments(adjustments),
      needsTemperatureTint: this._hasTemperatureTintAdjustments(adjustments)
    };
  }
  
  /**
   * Process all filters in a single pass
   * Requirement 3.3: Single pass over Image Data Buffer
   * Requirement 1.1: Complete filter application within 16ms
   * 
   * @param {ImageData} imageData - Input image data
   * @param {Object} adjustments - Filter parameters
   * @returns {ImageData} Processed image data
   */
  processPixels(imageData, adjustments) {
    // Requirement 1.1: Analyze which filters need processing (skip if all default)
    const filterReqs = this._analyzeFilterRequirements(adjustments);
    
    // Early exit if no filters are active
    // Requirement 1.1: Add checks to skip filters with default values
    if (!filterReqs.needsExposure && !filterReqs.needsOffset && !filterReqs.needsGamma && 
        !filterReqs.needsColorBalance && !filterReqs.needsBW && !filterReqs.needsVibrance &&
        !filterReqs.needsBasic && !filterReqs.needsTemperatureTint) {
      return imageData;
    }
    
    // Requirement 1.1: Batch similar operations together - Initialize lookup tables
    // Pre-calculate lookup tables for all filters that need them
    if (filterReqs.needsGamma) {
      this._initGammaLUT(adjustments.gammaCorrection);
    }
    
    if (filterReqs.needsExposure) {
      this._initExposureLUT(adjustments.exposure);
    }
    
    // Requirement 1.1: Optimize color space conversions (only when needed)
    // Determine if we need luminance calculation (for color balance)
    const needsLuminance = filterReqs.needsColorBalance;
    
    // Determine if we need HSL conversion (for B&W or certain color operations)
    const needsHSL = filterReqs.needsBW;
    
    // Create a copy of the image data using Uint8ClampedArray for automatic clamping
    // Requirement 1.5: Use Uint8ClampedArray for automatic value clamping
    const outputData = new ImageData(
      new Uint8ClampedArray(imageData.data),
      imageData.width,
      imageData.height
    );
    
    const data = outputData.data;
    
    // Single loop over all pixels
    for (let i = 0; i < data.length; i += 4) {
      let r = data[i];
      let g = data[i + 1];
      let b = data[i + 2];
      // Alpha channel at data[i + 3] is preserved
      
      // Requirement 1.1: Optimize color space conversions (only when needed)
      // Calculate luminance only if needed for color balance
      let lumNorm = 0;
      if (needsLuminance) {
        const lum = 0.299 * r + 0.587 * g + 0.114 * b;
        lumNorm = lum / 255;
      }
      
      // Requirement 1.1: Batch similar operations together
      // Group 1: Exposure-related operations (exposure, offset, gamma)
      // These all modify brightness/tonal values and can be batched
      if (filterReqs.needsExposure) {
        const result = this.applyExposure(r, g, b, adjustments.exposure);
        r = result.r;
        g = result.g;
        b = result.b;
      }
      
      if (filterReqs.needsOffset) {
        const offsetAmount = adjustments.offset * 2.55;
        r += offsetAmount;
        g += offsetAmount;
        b += offsetAmount;
      }
      
      if (filterReqs.needsGamma) {
        const result = this.applyGammaCorrection(r, g, b, adjustments.gammaCorrection);
        r = result.r;
        g = result.g;
        b = result.b;
      }
      
      // Group 2: Color operations (color balance, temperature/tint)
      // These modify color relationships
      if (filterReqs.needsColorBalance) {
        const result = this.applyColorBalance(r, g, b, lumNorm, adjustments);
        r = result.r;
        g = result.g;
        b = result.b;
      }
      
      if (filterReqs.needsTemperatureTint) {
        const result = this.applyTemperatureTint(r, g, b, adjustments);
        r = result.r;
        g = result.g;
        b = result.b;
      }
      
      // Group 3: Special effects (B&W conversion)
      // Requirement 1.1: Optimize color space conversions (only when needed)
      if (filterReqs.needsBW) {
        const bwResult = this.applyBlackAndWhite(r, g, b, adjustments);
        r = bwResult.r;
        g = bwResult.g;
        b = bwResult.b;
      }
      
      // Group 4: Saturation operations (vibrance, saturation)
      // These modify color intensity
      if (filterReqs.needsVibrance) {
        const vibResult = this.applyVibranceAndSaturation(r, g, b, adjustments);
        r = vibResult.r;
        g = vibResult.g;
        b = vibResult.b;
      }
      
      // Clamp values to 0-255 range (Uint8ClampedArray will do this automatically)
      data[i] = r;
      data[i + 1] = g;
      data[i + 2] = b;
      // data[i + 3] (alpha) remains unchanged
    }
    
    return outputData;
  }
  
  /**
   * Apply exposure adjustment using lookup table
   * Requirement 1.1: Exposure filter with lookup table optimization
   * 
   * @param {number} r - Red channel value
   * @param {number} g - Green channel value
   * @param {number} b - Blue channel value
   * @param {number} exposure - Exposure value (-2 to 2)
   * @returns {Object} Adjusted RGB values
   */
  applyExposure(r, g, b, exposure) {
    // Use lookup table if available
    if (this.exposureLUT) {
      return {
        r: this.exposureLUT[Math.round(r)],
        g: this.exposureLUT[Math.round(g)],
        b: this.exposureLUT[Math.round(b)]
      };
    }
    
    // Fallback to calculation
    const exposureFactor = Math.pow(2, exposure);
    return {
      r: r * exposureFactor,
      g: g * exposureFactor,
      b: b * exposureFactor
    };
  }
  
  /**
   * Apply gamma correction using lookup table
   * Requirement 1.1: Gamma correction filter with lookup table optimization
   * 
   * @param {number} r - Red channel value
   * @param {number} g - Green channel value
   * @param {number} b - Blue channel value
   * @param {number} gamma - Gamma value (0.1 to 3.0)
   * @returns {Object} Adjusted RGB values
   */
  applyGammaCorrection(r, g, b, gamma) {
    // Use lookup table if available
    if (this.gammaLUT) {
      return {
        r: this.gammaLUT[Math.round(r)],
        g: this.gammaLUT[Math.round(g)],
        b: this.gammaLUT[Math.round(b)]
      };
    }
    
    // Fallback to calculation
    const invGamma = 1 / gamma;
    return {
      r: 255 * Math.pow(r / 255, invGamma),
      g: 255 * Math.pow(g / 255, invGamma),
      b: 255 * Math.pow(b / 255, invGamma)
    };
  }
  
  /**
   * Apply temperature and tint adjustments
   * Requirement 1.1: Batch similar operations together
   * 
   * @param {number} r - Red channel value
   * @param {number} g - Green channel value
   * @param {number} b - Blue channel value
   * @param {Object} adjustments - Temperature and tint adjustments
   * @returns {Object} Adjusted RGB values
   */
  applyTemperatureTint(r, g, b, adjustments) {
    let newR = r;
    let newG = g;
    let newB = b;
    
    // Temperature adjustment (warm/cool)
    if (adjustments.temperature !== 0) {
      const tempFactor = adjustments.temperature / 100;
      if (tempFactor > 0) {
        // Warm (increase red/yellow)
        newR += tempFactor * 30;
        newG += tempFactor * 15;
        newB -= tempFactor * 10;
      } else {
        // Cool (increase blue)
        newR += tempFactor * 10;
        newG += tempFactor * 5;
        newB -= tempFactor * 30;
      }
    }
    
    // Tint adjustment (green/magenta)
    if (adjustments.tint !== 0) {
      const tintFactor = adjustments.tint / 100;
      if (tintFactor > 0) {
        // Magenta
        newR += tintFactor * 20;
        newG -= tintFactor * 10;
        newB += tintFactor * 20;
      } else {
        // Green
        newR += tintFactor * 10;
        newG -= tintFactor * 20;
        newB += tintFactor * 10;
      }
    }
    
    return { r: newR, g: newG, b: newB };
  }
  
  /**
   * Apply color balance (shadows, midtones, highlights)
   * Requirement 3.3: Color balance filter
   * 
   * @param {number} r - Red channel value
   * @param {number} g - Green channel value
   * @param {number} b - Blue channel value
   * @param {number} lumNorm - Normalized luminance (0-1)
   * @param {Object} adjustments - Color balance adjustments
   * @returns {Object} Adjusted RGB values
   */
  applyColorBalance(r, g, b, lumNorm, adjustments) {
    let newR = r;
    let newG = g;
    let newB = b;
    
    // Requirement 1.1: Skip calculations for ranges with no adjustments
    const hasShadowAdj = adjustments.shadowsCyan !== 0 || adjustments.shadowsMagenta !== 0 || adjustments.shadowsYellow !== 0;
    const hasMidtoneAdj = adjustments.midtonesCyan !== 0 || adjustments.midtonesMagenta !== 0 || adjustments.midtonesYellow !== 0;
    const hasHighlightAdj = adjustments.highlightsCyan !== 0 || adjustments.highlightsMagenta !== 0 || adjustments.highlightsYellow !== 0;
    
    // Shadows (dark areas, luminance < 0.33)
    // Requirement 1.1: Add checks to skip filters with default values
    if (lumNorm < 0.33 && hasShadowAdj) {
      const shadowWeight = (0.33 - lumNorm) / 0.33;
      newR += adjustments.shadowsCyan * shadowWeight * -0.5;
      newR += adjustments.shadowsMagenta * shadowWeight * 0.3;
      newR += adjustments.shadowsYellow * shadowWeight * 0.5;
      
      newG += adjustments.shadowsCyan * shadowWeight * 0.3;
      newG += adjustments.shadowsMagenta * shadowWeight * -0.5;
      newG += adjustments.shadowsYellow * shadowWeight * 0.5;
      
      newB += adjustments.shadowsCyan * shadowWeight * 0.5;
      newB += adjustments.shadowsMagenta * shadowWeight * 0.3;
      newB += adjustments.shadowsYellow * shadowWeight * -0.5;
    }
    
    // Midtones (luminance 0.33 to 0.66)
    // Requirement 1.1: Add checks to skip filters with default values
    if (lumNorm >= 0.33 && lumNorm <= 0.66 && hasMidtoneAdj) {
      const midWeight = 1 - Math.abs(lumNorm - 0.5) * 3;
      newR += adjustments.midtonesCyan * midWeight * -0.5;
      newR += adjustments.midtonesMagenta * midWeight * 0.3;
      newR += adjustments.midtonesYellow * midWeight * 0.5;
      
      newG += adjustments.midtonesCyan * midWeight * 0.3;
      newG += adjustments.midtonesMagenta * midWeight * -0.5;
      newG += adjustments.midtonesYellow * midWeight * 0.5;
      
      newB += adjustments.midtonesCyan * midWeight * 0.5;
      newB += adjustments.midtonesMagenta * midWeight * 0.3;
      newB += adjustments.midtonesYellow * midWeight * -0.5;
    }
    
    // Highlights (bright areas, luminance > 0.66)
    // Requirement 1.1: Add checks to skip filters with default values
    if (lumNorm > 0.66 && hasHighlightAdj) {
      const highlightWeight = (lumNorm - 0.66) / 0.34;
      newR += adjustments.highlightsCyan * highlightWeight * -0.5;
      newR += adjustments.highlightsMagenta * highlightWeight * 0.3;
      newR += adjustments.highlightsYellow * highlightWeight * 0.5;
      
      newG += adjustments.highlightsCyan * highlightWeight * 0.3;
      newG += adjustments.highlightsMagenta * highlightWeight * -0.5;
      newG += adjustments.highlightsYellow * highlightWeight * 0.5;
      
      newB += adjustments.highlightsCyan * highlightWeight * 0.5;
      newB += adjustments.highlightsMagenta * highlightWeight * 0.3;
      newB += adjustments.highlightsYellow * highlightWeight * -0.5;
    }
    
    return { r: newR, g: newG, b: newB };
  }
  
  /**
   * Apply black & white conversion with color channel mixing
   * Requirement 3.3: Black & white conversion
   * Requirement 1.1: Use cached color space conversion
   * 
   * @param {number} r - Red channel value
   * @param {number} g - Green channel value
   * @param {number} b - Blue channel value
   * @param {Object} adjustments - B&W adjustments
   * @returns {Object} Adjusted RGB values
   */
  applyBlackAndWhite(r, g, b, adjustments) {
    // Use cached RGB to HSL conversion
    const hsl = this._rgbToHSLCached(Math.round(r), Math.round(g), Math.round(b));
    const h = hsl.h;
    
    // Determine which color range this pixel belongs to
    let colorWeight = 0;
    
    if (h >= 345 || h < 15) {
      colorWeight = adjustments.bwRed / 100;
    } else if (h >= 15 && h < 45) {
      colorWeight = adjustments.bwYellow / 100;
    } else if (h >= 45 && h < 75) {
      colorWeight = adjustments.bwYellow / 100;
    } else if (h >= 75 && h < 155) {
      colorWeight = adjustments.bwGreen / 100;
    } else if (h >= 155 && h < 185) {
      colorWeight = adjustments.bwCyan / 100;
    } else if (h >= 185 && h < 255) {
      colorWeight = adjustments.bwBlue / 100;
    } else if (h >= 255 && h < 345) {
      colorWeight = adjustments.bwMagenta / 100;
    }
    
    // Calculate grayscale with color-specific weighting
    let gray = (0.299 * r + 0.587 * g + 0.114 * b) * colorWeight;
    
    // Apply tint
    let newR = gray;
    let newG = gray;
    let newB = gray;
    
    if (adjustments.bwTint !== 0) {
      const tintAmount = adjustments.bwTint * 0.8;
      if (adjustments.bwTint > 0) {
        // Warm tint (sepia)
        newR += tintAmount;
        newG += tintAmount * 0.7;
        newB += tintAmount * 0.3;
      } else {
        // Cool tint (blue)
        newR += tintAmount * 0.3;
        newG += tintAmount * 0.5;
        newB -= tintAmount;
      }
    }
    
    return { r: newR, g: newG, b: newB };
  }
  
  /**
   * Apply vibrance and saturation
   * Requirement 3.3: Vibrance and saturation filters
   * Requirement 1.1: Batch similar operations together
   * 
   * @param {number} r - Red channel value
   * @param {number} g - Green channel value
   * @param {number} b - Blue channel value
   * @param {Object} adjustments - Vibrance and saturation adjustments
   * @returns {Object} Adjusted RGB values
   */
  applyVibranceAndSaturation(r, g, b, adjustments) {
    let newR = r;
    let newG = g;
    let newB = b;
    
    // Requirement 1.1: Batch similar operations together
    // Calculate average once if either vibrance or saturation is active
    const needsAvg = adjustments.vibrance !== 0 || adjustments.saturationVibrance !== 0;
    let avg = 0;
    
    if (needsAvg) {
      avg = (r + g + b) / 3;
    }
    
    // Vibrance (smart saturation - affects less saturated colors more)
    // Requirement 1.1: Add checks to skip filters with default values
    if (adjustments.vibrance !== 0) {
      const maxRGB = Math.max(r, g, b);
      const currentSat = maxRGB > 0 ? (maxRGB - avg) / maxRGB : 0;
      
      // Apply vibrance more to less saturated colors
      const vibranceFactor = (1 - currentSat) * (adjustments.vibrance / 100);
      
      newR = avg + (r - avg) * (1 + vibranceFactor);
      newG = avg + (g - avg) * (1 + vibranceFactor);
      newB = avg + (b - avg) * (1 + vibranceFactor);
    }
    
    // Saturation (uniform adjustment)
    // Requirement 1.1: Add checks to skip filters with default values
    if (adjustments.saturationVibrance !== 0) {
      // Recalculate average if vibrance was applied
      if (adjustments.vibrance !== 0) {
        avg = (newR + newG + newB) / 3;
      }
      
      const satFactor = 1 + (adjustments.saturationVibrance / 100);
      
      newR = avg + (newR - avg) * satFactor;
      newG = avg + (newG - avg) * satFactor;
      newB = avg + (newB - avg) * satFactor;
    }
    
    return { r: newR, g: newG, b: newB };
  }
}

console.log('[FilterWorker] Loaded and ready');
