const fs = require('fs');
const path = require('path');

// WGS84 ellipsoid parameters
const a = 6378137.0; // Semi-major axis in meters
const f = 1 / 298.257223563; // Flattening
const b = a * (1 - f); // Semi-minor axis in meters
const e2 = (a * a - b * b) / (a * a); // Eccentricity squared
const e_prime2 = (a * a - b * b) / (b * b); // Second eccentricity squared

/**
 * Converts ECEF coordinates to latitude, longitude, and height
 * @param {number} x - X coordinate in ECEF
 * @param {number} y - Y coordinate in ECEF
 * @param {number} z - Z coordinate in ECEF
 * @returns {Object} Latitude, longitude, and height
 */
function ecefToLlh(x, y, z) {
    let lon = Math.atan2(y, x);
    let p = Math.sqrt(x * x + y * y);
    let theta = Math.atan2(z * a, p * b);
    
    let lat = Math.atan2(z + e_prime2 * b * Math.sin(theta)**3, p - e2 * a * Math.cos(theta)**3);
    
    // Refine latitude
    let N = a / Math.sqrt(1 - e2 * Math.sin(lat)**2);
    let h = p / Math.cos(lat) - N;
    
    // Convert radians to degrees
    lat = lat * 180 / Math.PI;
    lon = lon * 180 / Math.PI;
    
    return { lat, lon, height: h };
}

/**
 * Applies a transform matrix to a vertex
 * @param {Array} vertex - 3D vertex [x, y, z]
 * @param {Array} transform - 4x4 transform matrix
 * @returns {Array} Transformed vertex [x, y, z]
 */
function applyTransform(vertex, transform) {
    const [x, y, z] = vertex;
    
    // Ensure we have a 4x4 matrix
    const t = new Array(16).fill(0);
    
    // Set identity matrix as default
    t[0] = 1;
    t[5] = 1;
    t[10] = 1;
    t[15] = 1;
    
    // Copy transform values (handle both 12-element and 16-element matrices)
    for (let i = 0; i < Math.min(16, transform.length); i++) {
        t[i] = transform[i];
    }
    
    // Apply 4x4 matrix
    const newX = t[0] * x + t[4] * y + t[8] * z + t[12];
    const newY = t[1] * x + t[5] * y + t[9] * z + t[13];
    const newZ = t[2] * x + t[6] * y + t[10] * z + t[14];
    const w = t[3] * x + t[7] * y + t[11] * z + t[15];
    
    // Normalize if w is not 1
    if (w !== 1 && w !== 0) {
        return [newX / w, newY / w, newZ / w];
    }
    
    return [newX, newY, newZ];
}

/**
 * Parses a box from tileset.json
 * @param {Array} box - Box array from tileset.json
 * @param {Array} transform - Transform matrix from tileset.json
 * @returns {Object} Box information including vertices and bounds
 */
function parseBox(box, transform) {
    const center = box.slice(0, 3);
    const xAxis = box.slice(3, 6);
    const yAxis = box.slice(6, 9);
    const zAxis = box.slice(9, 12);
    
    // Calculate 8 vertices of the box
    const vertices = [
        [center[0] - xAxis[0] - yAxis[0] - zAxis[0], center[1] - xAxis[1] - yAxis[1] - zAxis[1], center[2] - xAxis[2] - yAxis[2] - zAxis[2]],
        [center[0] + xAxis[0] - yAxis[0] - zAxis[0], center[1] + xAxis[1] - yAxis[1] - zAxis[1], center[2] + xAxis[2] - yAxis[2] - zAxis[2]],
        [center[0] - xAxis[0] + yAxis[0] - zAxis[0], center[1] - xAxis[1] + yAxis[1] - zAxis[1], center[2] - xAxis[2] + yAxis[2] - zAxis[2]],
        [center[0] + xAxis[0] + yAxis[0] - zAxis[0], center[1] + xAxis[1] + yAxis[1] - zAxis[1], center[2] + xAxis[2] + yAxis[2] - zAxis[2]],
        [center[0] - xAxis[0] - yAxis[0] + zAxis[0], center[1] - xAxis[1] - yAxis[1] + zAxis[1], center[2] - xAxis[2] - yAxis[2] + zAxis[2]],
        [center[0] + xAxis[0] - yAxis[0] + zAxis[0], center[1] + xAxis[1] - yAxis[1] + zAxis[1], center[2] + xAxis[2] - yAxis[2] + zAxis[2]],
        [center[0] - xAxis[0] + yAxis[0] + zAxis[0], center[1] - xAxis[1] + yAxis[1] + zAxis[1], center[2] - xAxis[2] + yAxis[2] + zAxis[2]],
        [center[0] + xAxis[0] + yAxis[0] + zAxis[0], center[1] + xAxis[1] + yAxis[1] + zAxis[1], center[2] + xAxis[2] + yAxis[2] + zAxis[2]]
    ];
    
    // Apply transform to all vertices
    const transformedVertices = vertices.map(vertex => applyTransform(vertex, transform));
    
    // Convert to geographic coordinates
    const geoVertices = transformedVertices.map(vertex => ecefToLlh(vertex[0], vertex[1], vertex[2]));
    
    // Calculate min and max bounds
    const minLon = Math.min(...geoVertices.map(v => v.lon));
    const maxLon = Math.max(...geoVertices.map(v => v.lon));
    const minLat = Math.min(...geoVertices.map(v => v.lat));
    const maxLat = Math.max(...geoVertices.map(v => v.lat));
    const minHeight = Math.min(...geoVertices.map(v => v.height));
    const maxHeight = Math.max(...geoVertices.map(v => v.height));
    
    return {
        center,
        axes: { xAxis, yAxis, zAxis },
        vertices,
        transformedVertices,
        geoVertices,
        bounds: {
            min: { lon: minLon, lat: minLat, height: minHeight },
            max: { lon: maxLon, lat: maxLat, height: maxHeight }
        }
    };
}

/**
 * Parses tileset.json
 * @param {string} tilesetPath - Path to tileset.json
 * @returns {Object} Parsed tileset information
 */
function parseTileset(tilesetPath) {
    try {
        const tilesetContent = fs.readFileSync(tilesetPath, 'utf8');
        const tileset = JSON.parse(tilesetContent);
        
        const result = {
            asset: tileset.asset,
            extras: tileset.extras,
            geometricError: tileset.geometricError,
            refine: tileset.refine,
            tilesCount: 0,
            root: {}
        };
        
        // Parse root node
        if (tileset.root) {
            const root = tileset.root;
            const transform = root.transform || [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1];
            
            result.root = {
                geometricError: root.geometricError,
                refine: root.refine
            };
            
            // Parse bounding volume
            if (root.boundingVolume) {
                result.root.boundingVolume = {};
                
                if (root.boundingVolume.box) {
                    result.root.boundingVolume.box = parseBox(root.boundingVolume.box, transform);
                }
                
                if (root.boundingVolume.sphere) {
                    const sphere = root.boundingVolume.sphere;
                    const center = sphere.slice(0, 3);
                    const radius = sphere[3];
                    const transformedCenter = applyTransform(center, transform);
                    const geoCenter = ecefToLlh(transformedCenter[0], transformedCenter[1], transformedCenter[2]);
                    
                    result.root.boundingVolume.sphere = {
                        center,
                        radius,
                        transformedCenter,
                        geoCenter
                    };
                }
                
                if (root.boundingVolume.region) {
                    const region = root.boundingVolume.region;
                    const west = region[0] * 180 / Math.PI;
                    const south = region[1] * 180 / Math.PI;
                    const east = region[2] * 180 / Math.PI;
                    const north = region[3] * 180 / Math.PI;
                    const minHeight = region[4];
                    const maxHeight = region[5];
                    
                    result.root.boundingVolume.region = {
                        west,
                        south,
                        east,
                        north,
                        minHeight,
                        maxHeight
                    };
                }
            }
            
            // Parse children
            if (root.children) {
                result.root.children = root.children.map((child, index) => {
                    const childTransform = child.transform || transform;
                    const childResult = {
                        index,
                        geometricError: child.geometricError,
                        refine: child.refine,
                        content: child.content
                    };
                    
                    // Parse bounding volume
                    if (child.boundingVolume) {
                        childResult.boundingVolume = {};
                        
                        if (child.boundingVolume.box) {
                            childResult.boundingVolume.box = parseBox(child.boundingVolume.box, childTransform);
                        }
                        
                        if (child.boundingVolume.sphere) {
                            const sphere = child.boundingVolume.sphere;
                            const center = sphere.slice(0, 3);
                            const radius = sphere[3];
                            const transformedCenter = applyTransform(center, childTransform);
                            const geoCenter = ecefToLlh(transformedCenter[0], transformedCenter[1], transformedCenter[2]);
                            
                            childResult.boundingVolume.sphere = {
                                center,
                                radius,
                                transformedCenter,
                                geoCenter
                            };
                        }
                    }
                    
                    return childResult;
                });
                
                result.tilesCount = root.children.length;
            }
        }
        
        return result;
    } catch (error) {
        console.error(`Error parsing tileset.json: ${error.message}`);
        return null;
    }
}

/**
 * Parses scenetree.json
 * @param {string} scenetreePath - Path to scenetree.json
 * @returns {Object} Parsed scenetree information
 */
function parseScenetree(scenetreePath) {
    try {
        const scenetreeContent = fs.readFileSync(scenetreePath, 'utf8');
        const scenetree = JSON.parse(scenetreeContent);
        
        // Convert sphere centers to geographic coordinates
        function processNode(node) {
            if (node.sphere) {
                const center = node.sphere.slice(0, 3);
                const radius = node.sphere[3];
                const geoCenter = ecefToLlh(center[0], center[1], center[2]);
                
                node.sphere = {
                    center,
                    radius,
                    geoCenter
                };
            }
            
            if (node.children) {
                node.children = node.children.map(processNode);
            }
            
            return node;
        }
        
        const processedScenes = scenetree.scenes.map(processNode);
        
        return { scenes: processedScenes };
    } catch (error) {
        console.error(`Error parsing scenetree.json: ${error.message}`);
        return null;
    }
}

/**
 * Main function
 */
function main() {
    // Get command line arguments
    const args = process.argv.slice(2);
    
    if (args.length === 0) {
        console.error('Usage: node parse_3dtiles.js <tileset.json> [scenetree.json]');
        process.exit(1);
    }
    
    const tilesetPath = args[0];
    const scenetreePath = args[1] || path.join(path.dirname(tilesetPath), 'scenetree.json');
    
    console.log('Parsing 3D Tiles files...');
    console.log(`Tileset: ${tilesetPath}`);
    console.log(`Scenetree: ${scenetreePath}`);
    
    // Parse tileset.json
    const tilesetData = parseTileset(tilesetPath);
    if (!tilesetData) {
        console.error('Failed to parse tileset.json');
        process.exit(1);
    }
    
    // Parse scenetree.json if it exists
    let scenetreeData = null;
    if (fs.existsSync(scenetreePath)) {
        scenetreeData = parseScenetree(scenetreePath);
        if (!scenetreeData) {
            console.warn('Failed to parse scenetree.json, continuing without it');
        }
    } else {
        console.warn('scenetree.json not found, continuing without it');
    }
    
    // Combine results
    const result = {
        tileset: tilesetData,
        scenetree: scenetreeData
    };
    
    // Output results
    const outputPath = path.join(path.dirname(tilesetPath), '3dtiles_info.json');
    fs.writeFileSync(outputPath, JSON.stringify(result, null, 2), 'utf8');
    
    console.log('\nParsing completed successfully!');
    console.log(`Results saved to: ${outputPath}`);
    
    // Print summary
    console.log('\nSummary:');
    console.log(`- Asset: ${tilesetData.asset.generatetool || 'Unknown'}, Version: ${tilesetData.asset.version}`);
    console.log(`- Total tiles: ${tilesetData.tilesCount}`);
    
    if (tilesetData.root.boundingVolume && tilesetData.root.boundingVolume.box) {
        const bounds = tilesetData.root.boundingVolume.box.bounds;
        console.log('\nGeographic bounds (root):');
        console.log(`- Min: Longitude ${bounds.min.lon.toFixed(6)}, Latitude ${bounds.min.lat.toFixed(6)}, Height ${bounds.min.height.toFixed(2)}m`);
        console.log(`- Max: Longitude ${bounds.max.lon.toFixed(6)}, Latitude ${bounds.max.lat.toFixed(6)}, Height ${bounds.max.height.toFixed(2)}m`);
    }
    
    if (scenetreeData) {
        console.log('\nScenetree nodes:', scenetreeData.scenes.length);
    }
}

// Run main function if called directly
if (require.main === module) {
    main();
}

// Export functions for use in other modules
module.exports = {
    ecefToLlh,
    applyTransform,
    parseBox,
    parseTileset,
    parseScenetree
};