#import <Cocoa/Cocoa.h>
#import <SceneKit/SceneKit.h>
#include <string>
#include <iostream>
#include <thread>
#include <array>

// --- Helper for executing shell commands ---
std::string exec(const char* cmd) {
    std::array<char, 128> buffer;
    std::string result;
    std::unique_ptr<FILE, decltype(&pclose)> pipe(popen(cmd, "r"), pclose);
    if (!pipe) return "";
    while (fgets(buffer.data(), buffer.size(), pipe.get()) != nullptr) {
        result += buffer.data();
    }
    return result;
}

@interface VirtualCD3D : NSObject <NSApplicationDelegate> {
    NSWindow *window;
    SCNView *scnView;
    SCNNode *driveNode;
    SCNNode *cdNode;
    std::string devicePath;
}
@property (nonatomic, strong) NSString *targetUrl;
@property (nonatomic, strong) NSString *fileSizeGB;
@end

@implementation VirtualCD3D

- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
    // 1. Ask for URL via native dialog
    NSAlert *alert = [[NSAlert alloc] init];
    [alert setMessageText:@"Insert Virtual CD"];
    [alert setInformativeText:@"Enter the URL of the file to mount:"];
    [alert addButtonWithTitle:@"Insert"];
    [alert addButtonWithTitle:@"Cancel"];
    
    NSTextField *input = [[NSTextField alloc] initWithFrame:NSMakeRect(0, 0, 300, 24)];
    [input setStringValue:self.targetUrl ? self.targetUrl : @"https://www.google.com/favicon.ico"];
    [alert setAccessoryView:input];
    
    if ([alert runModal] == NSAlertSecondButtonReturn) {
        [NSApp terminate:nil];
        return;
    }
    self.targetUrl = [input stringValue];

    NSRect frame = NSMakeRect(0, 0, 800, 600);
    window = [[NSWindow alloc] initWithContentRect:frame
                                          styleMask:(NSWindowStyleMaskTitled | NSWindowStyleMaskClosable)
                                            backing:NSBackingStoreBuffered
                                              defer:NO];
    [window setTitle:@"3D Virtual CD Drive (Native)"];
    [window makeKeyAndOrderFront:nil];
    [window center];
    [window setBackgroundColor:[NSColor blackColor]];

    scnView = [[SCNView alloc] initWithFrame:frame];
    scnView.backgroundColor = [NSColor blackColor];
    scnView.autoenablesDefaultLighting = YES;
    [window setContentView:scnView];

    SCNScene *scene = [SCNScene scene];
    scnView.scene = scene;

    // Camera at 40 degree angle
    SCNNode *cameraNode = [SCNNode node];
    cameraNode.camera = [SCNCamera camera];
    cameraNode.position = SCNVector3Make(0, 5, 12);
    cameraNode.eulerAngles = SCNVector3Make(-M_PI / 180 * 30, 0, 0);
    [scene.rootNode addChildNode:cameraNode];

    // Drive Body
    SCNBox *driveBox = [SCNBox boxWithWidth:6 height:1 length:8 chamferRadius:0.1];
    driveBox.firstMaterial.diffuse.contents = [NSColor darkGrayColor];
    driveNode = [SCNNode nodeWithGeometry:driveBox];
    driveNode.eulerAngles = SCNVector3Make(M_PI / 180 * 40, 0, 0); // 40 degree tilt
    [scene.rootNode addChildNode:driveNode];

    // CD Model
    SCNCylinder *cdGeo = [SCNCylinder cylinderWithRadius:2 height:0.02];
    
    // Bottom: Reflective
    SCNMaterial *bottomMat = [SCNMaterial material];
    bottomMat.diffuse.contents = [NSColor whiteColor];
    bottomMat.lightingModelName = SCNLightingModelPhysicallyBased;
    bottomMat.metalness.contents = @1.0;
    bottomMat.roughness.contents = @0.1;
    
    // Top: Text with URL and Size
    SCNMaterial *topMat = [SCNMaterial material];
    topMat.diffuse.contents = [NSColor lightGrayColor];
    
    cdGeo.materials = @[topMat, bottomMat, topMat]; 
    cdNode = [SCNNode nodeWithGeometry:cdGeo];
    
    // Position CD at the FRONT of the tilted drive
    [driveNode addChildNode:cdNode];
    cdNode.position = SCNVector3Make(0, 0.2, 10); // Start 10 units in front of slot
    cdNode.opacity = 0;

    // Add Text to CD Top
    SCNText *textGeo = [SCNText textWithString:[NSString stringWithFormat:@"%@\nCalculating...", self.targetUrl] extrusionDepth:0.01];
    textGeo.font = [NSFont systemFontOfSize:0.15];
    textGeo.flatness = 0.01;
    SCNNode *textNode = [SCNNode nodeWithGeometry:textGeo];
    textNode.position = SCNVector3Make(-1.5, 0.02, 0.5);
    textNode.eulerAngles = SCNVector3Make(-M_PI/2, 0, 0);
    textNode.geometry.firstMaterial.diffuse.contents = [NSColor blackColor];
    [cdNode addChildNode:textNode];

    [self startAnimation];
    [self performSelectorInBackground:@selector(mountProcess) withObject:nil];
}

- (void)startAnimation {
    [SCNTransaction begin];
    [SCNTransaction setAnimationDuration:2.5];
    cdNode.opacity = 1.0;
    cdNode.position = SCNVector3Make(0, 0.2, 0); // Slide into slot
    [SCNTransaction commit];
}

- (void)mountProcess {
    try {
        std::string url = [self.targetUrl UTF8String];
        std::string sizeCmd = "curl -sI " + url + " | grep -i Content-Length | awk '{print $2}'";
        std::string sizeRaw = exec(sizeCmd.c_str());
        long bytes = std::atol(sizeRaw.c_str());
        if (bytes == 0) bytes = 100 * 1024 * 1024;
        
        double gb = bytes / 1024.0 / 1024.0 / 1024.0;
        self.fileSizeGB = [NSString stringWithFormat:@"%.4f GB", gb];

        // Update text on CD
        dispatch_async(dispatch_get_main_queue(), ^{
             SCNNode *textNode = self->cdNode.childNodes.firstObject;
             SCNText *textGeo = (SCNText *)textNode.geometry;
             textGeo.string = [NSString stringWithFormat:@"%@\n%@", self.targetUrl, self.fileSizeGB];
        });

        // Download
        exec(("curl -L -o /tmp/vcd_dl " + url).c_str());

        // Mount
        long sectors = (long)((bytes * 1.3 + 20*1024*1024) / 512);
        std::string dev = exec(("hdiutil attach -nomount ram://" + std::to_string(sectors)).c_str());
        dev.erase(dev.find_last_not_of(" \n\r\t")+1);
        devicePath = dev;
        
        exec(("diskutil eraseVolume HFS+ VirtualCD " + dev).c_str());
        exec("cp /tmp/vcd_dl /Volumes/VirtualCD/");
        exec("open /Volumes/VirtualCD");

        // Wait for eject
        while (true) {
            std::string status = exec(("diskutil info " + devicePath + " 2>&1").c_str());
            if (status.find("Could not find disk") != std::string::npos) break;
            std::this_thread::sleep_for(std::chrono::seconds(1));
        }

        // Eject animation: Slide out and fall to void
        dispatch_async(dispatch_get_main_queue(), ^{
            [SCNTransaction begin];
            [SCNTransaction setAnimationDuration:2.0];
            self->cdNode.position = SCNVector3Make(0, 0.2, 12); // Slide out
            [SCNTransaction setCompletionBlock:^{
                [SCNTransaction begin];
                [SCNTransaction setAnimationDuration:3.0];
                self->cdNode.position = SCNVector3Make(0, -50, 20); // Fall to void
                self->cdNode.opacity = 0;
                [SCNTransaction setCompletionBlock:^{
                    [NSApp terminate:nil];
                }];
                [SCNTransaction commit];
            }];
            [SCNTransaction commit];
        });

    } catch (...) {}
}

@end

int main(int argc, char * argv[]) {
    @autoreleasepool {
        NSApplication *app = [NSApplication sharedApplication];
        VirtualCD3D *delegate = [[VirtualCD3D alloc] init];
        delegate.targetUrl = (argc > 1) ? [NSString stringWithUTF8String:argv[1]] : nil;
        delegate.fileSizeGB = @"Calculating...";
        [app setDelegate:delegate];
        [app run];
    }
    return 0;
}
