// Copyright (C) 2003-2026, Foxit Software Inc..
// All Rights Reserved.
//
// http://www.foxitsoftware.com
//
// The following code is copyrighted and contains proprietary information and trade secrets of Foxit Software Inc..
// You cannot distribute any part of Foxit PDF SDK to any third party or general public,
// unless there is a separate license agreement with Foxit Software Inc. which explicitly grants you such rights.
//
// This file contains an example to demonstrate how to use Foxit PDF SDK to add, sign, verify and get PAdES level of
// a PAdES signature in PDF document.

#include "FSPDFObjC.h"

#include <string>

NSString *input_path = @"../input_files/";
NSString *output_path = @"../output_files/";

NSString *sn = @"bPxThFlBAYifO3yiW0C51bKbjPap1zHaf5aG5sX378TjFvB7Q0802A==";
NSString *key = @"8f3g1sGNtR8NAgfSNv+BvaJWy2fnrw36NZvzh8GUxYXvs+/YczAk4e34yqlOnmL4asgxGMO7+rlOcSHUSSKagx5g4G7VEXfkYLXnDDx77FOOedZPrFFcOCu1vT4+wOKzy1zkORq9cKtiFf44byH5sfRqf+OvELjep2teT9eF9yPCsSw2vRKljXlPy40DMP9XX1O5OTe+mSG3RvwKUMb0+RpG3YobM87j+/ynTDlJMMXw4vgwuTb1ZuAtlvkTdFz7HDLV81C6TYnn73ddHeGD3qYWBVsD4uJkDwyyH/SWr7QX0HSgVg8HSZqnab1VGdAUYxKY2vGWeOTJdu6qDGb5ZEb9O3kZZg+9LYAmUKzDfCYwfIsYZ8Y9LPC4wMx70kIq7JpNnzPvigrdshV8/ew1F0+qP/P5Z1EB53vDVrp2LVSuL873Wv4g3PzjZlNH+1qgT87hC+Buto4qsSUfP5Y2z0uApu+c/TfttCXbeMLSsw9TY1HQD3mkrZExMaQqRrAuXAcZWdvBnVuG3DCZmm8q0DLxJiCfYfViAU/AkbeKMIGaTJdrvIXBcssfde4nmU6qr0M4x4+Hs2ESddtY2QRtLAUBq/7h7GJlJHkEZr8ueaUy/6D/2vvAWz6wMLgmjlPbDTLqzfnCJylHCPtcJmu1SPPT9v1VreBX3+zNWcIT8HueyHEiUniqzwWkRKaotrnNb/zhnHLPpgaBSwKjyi8rOzzVwu/nY84G9ye4jsVjmkIRItKDrW7z22iS+QQuTwfqLVuMB0OD4jMkPCC2slzPfEeZnuh4+Rewln2qLRJVgI72zJ20OBWAelGSpO1Ii2Y3gOvojdmtUwx6R3Z1qFNWTTB25o9ytcux8BLOtTCkA8T8xaT4N339OTCsu/TmfahIDixKvr8cDa6F2WkBT8SOfv64W9PgiNv5B/BMOD2VWYaAl0N7yD0NkYYg/cDWRC4dfpdeip1jrr0k3mND0oSpIkxC+FicZbaYYwRdtJ9OSVWJ5OE0voLv8HgLze8mw6s4TrN3z+a2NQCveMa2VRN7YcEF7agPrvb/qU+otMwHae/wM7+f+HFAaOW+WTfzfxIVFB8/A1DN0NiEX+em4G7nO5F5ZcvfYPpaaioe8Nn33BbVLtMjXqsOBeM8AXiVfpD6YO75j7BqnBUPnivvD8BxJaaIQZvVSuQmvGm0ynt03IYwE8Rq+jYsunvsZ86jh5BiUR+Nm7sK0Qe9BdZn/7jdqohMv9czfMGUAscX+xBMunFbDpcRbWVpUADcm209L8Ndek2XY7YlCMxPgPVNvfWRF2jxehib9Tpd7zB5ZIDfAEH9zrpLfp29DLvCh2Yx8mH7xxdKG1O9Q0GdHphYHhej6smAcPm68R0GUQaTOLfXqNpueGJC1GAOI3Y4swjyt7jMTWcibnKLzwukWE5rnEDhn5n64JnyUM0ZFkRmtOiV";

////////////////////////

std::string TransformSignatureStateToString(uint32 sig_state) {
    std::string state_str;
    if ((sig_state & FSSignatureStateUnknown) == FSSignatureStateUnknown)
        state_str += "Unknown";
    if (sig_state & FSSignatureStateNoSignData){
        if (state_str.length() > 0)
            state_str += "|";
        state_str += "NoSignData";
    }
    if (sig_state & FSSignatureStateUnsigned) {
        if (state_str.length() > 0)
            state_str += "|";
        state_str += "Unsigned";
    }
    if (sig_state & FSSignatureStateSigned) {
        if (state_str.length() > 0)
            state_str += "|";
        state_str += "Signed";
    }
    if (sig_state & FSSignatureStateVerifyValid) {
        if (state_str.length() > 0)
            state_str += "|";
        state_str += "VerfiyValid";
    }
    if (sig_state & FSSignatureStateVerifyInvalid) {
        if (state_str.length() > 0)
            state_str += "|";
        state_str += "VerifyInvalid";
    }
    if (sig_state & FSSignatureStateVerifyErrorData) {
        if (state_str.length() > 0)
            state_str += "|";
        state_str += "VerifyErrorData";
    }
    if (sig_state & FSSignatureStateVerifyNoSupportWay) {
        if (state_str.length() > 0)
            state_str += "|";
        state_str += "VerifyNoSupportWay";
    }
    if (sig_state & FSSignatureStateVerifyErrorByteRange) {
        if (state_str.length() > 0)
            state_str += "|";
        state_str += "VerifyErrorByteRange";
    }
    if (sig_state & FSSignatureStateVerifyChange) {
        if (state_str.length() > 0)
            state_str += "|";
        state_str += "VerifyChange";
    }
    if (sig_state & FSSignatureStateVerifyIncredible) {
        if (state_str.length() > 0)
            state_str += "|";
        state_str += "VerifyIncredible";
    }
    if (sig_state & FSSignatureStateVerifyNoChange) {
        if (state_str.length() > 0)
            state_str += "|";
        state_str += "VerifyNoChange";
    }
    if (sig_state & FSSignatureStateVerifyIssueValid) {
        if (state_str.length() > 0)
            state_str += "|";
        state_str += "VerifyIssueValid";
    }
    if (sig_state & FSSignatureStateVerifyIssueUnknown) {
        if (state_str.length() > 0)
            state_str += "|";
        state_str += "VerifyIssueUnknown";
    }
    if (sig_state & FSSignatureStateVerifyIssueRevoke) {
        if (state_str.length() > 0)
            state_str += "|";
        state_str += "VerifyIssueRevoke";
    }
    if (sig_state & FSSignatureStateVerifyIssueExpire) {
        if (state_str.length() > 0)
            state_str += "|";
        state_str += "VerifyIssueExpire";
    }
    if (sig_state & FSSignatureStateVerifyIssueUncheck) {
        if (state_str.length() > 0)
            state_str += "|";
        state_str += "VerifyIssueUncheck";
    }
    if (sig_state & FSSignatureStateVerifyIssueCurrent) {
        if (state_str.length() > 0)
            state_str += "|";
        state_str += "VerifyIssueCurrent";
    }
    if (sig_state & FSSignatureStateVerifyTimestampNone) {
        if (state_str.length() > 0)
            state_str += "|";
        state_str += "VerifyTimestampNone";
    }
    if (sig_state & FSSignatureStateVerifyTimestampDoc) {
        if (state_str.length() > 0)
            state_str += "|";
        state_str += "VerifyTimestampDoc";
    }
    if (sig_state & FSSignatureStateVerifyTimestampValid) {
        if (state_str.length() > 0)
            state_str += "|";
        state_str += "VerifyTimestampValid";
    }
    if (sig_state & FSSignatureStateVerifyTimestampInvalid) {
        if (state_str.length() > 0)
            state_str += "|";
        state_str += "VerifyTimestampInvalid";
    }
    if (sig_state & FSSignatureStateVerifyTimestampExpire) {
        if (state_str.length() > 0)
            state_str += "|";
        state_str += "VerifyTimestampExpire";
    }
    if (sig_state & FSSignatureStateVerifyTimestampIssueUnknown) {
        if (state_str.length() > 0)
            state_str += "|";
        state_str += "VerifyTimestampIssueUnknown";
    }
    if (sig_state & FSSignatureStateVerifyTimestampIssueValid) {
        if (state_str.length() > 0)
            state_str += "|";
        state_str += "VerifyTimestampIssueValid";
    }
    if (sig_state & FSSignatureStateVerifyTimestampTimeBefore) {
        if (state_str.length() > 0)
            state_str += "|";
        state_str += "VerifyTimestampTimeBefore";
    }
    if (sig_state & FSSignatureStateCertCannotGetVRI) {
        if (state_str.length()>0) state_str += "|";
        state_str += "CertCannotGetVRI";
    }
    return state_str;
}

NSString* TransformLevel2NSString(FSSignaturePAdESLevel level) {
    switch (level) {
        case FSSignaturePAdESLevelNotPAdES:
            return @"NotPades";
        case FSSignaturePAdESLevelNone:
            return @"NoneLevel";
        case FSSignaturePAdESLevelBB:
            return @"LevelB";
        case FSSignaturePAdESLevelBT:
            return @"LevelT";
        case FSSignaturePAdESLevelBLT:
            return @"LevelLT";
        case FSSignaturePAdESLevelBLTA:
            return @"LevelLTA";
        default:
            return @"Unknown level value";
    }
}

FSDateTime *GetLocalDateTime() {
    time_t t = time(NULL);
    struct tm *rime = localtime(&t);
    tzset();
    int timezone_hour = (int) (timezone / 3600 * -1);
    int timezone_minute = (int) (timezone % 3600) / 60;
    
    FSDateTime *datetime = [[FSDateTime alloc] init];
    datetime.year = static_cast<uint16>(rime->tm_year + 1900);
    datetime.month = static_cast<uint16>(rime->tm_mon + 1);
    datetime.day = static_cast<uint16>(rime->tm_mday);
    datetime.hour = static_cast<uint16>(rime->tm_hour);
    datetime.minute = static_cast<uint16>(rime->tm_min);
    datetime.second = static_cast<uint16>(rime->tm_sec);
    datetime.utc_hour_offset = timezone_hour;
    datetime.utc_minute_offset = timezone_minute;
    
    return datetime;
}

void PAdESSign(NSString* input_pdf_path, NSString* signed_pdf_path, FSSignaturePAdESLevel pades_level) {
    printf("To add a PAdES signature in %s\r\n", [TransformLevel2NSString(pades_level) UTF8String]);
    NSString* cached_signed_pdf_path = signed_pdf_path;
    NSString* real_signed_pdf_path = signed_pdf_path;
    if (pades_level > FSSignaturePAdESLevelBT) {
        NSString* temp_substring = [signed_pdf_path substringToIndex:(signed_pdf_path.length-4)];
        cached_signed_pdf_path = [temp_substring stringByAppendingString:@"_cache.pdf"];
    }
    FSPDFDoc *pdf_doc = [[FSPDFDoc alloc] initWithPath:input_pdf_path];
    [pdf_doc startLoad:nil is_cache_stream:NO pause:nil];
    FSPDFPage *pdf_page = [pdf_doc getPage:0];
    
    float page_height = [pdf_page getHeight];
    float page_width = [pdf_page getWidth];
    FSRectF *new_sig_rect = [[FSRectF alloc] initWithLeft1:(float)(page_width / 2 - 50.0f) bottom1:(float)(page_height / 2 - 50.0f) right1:(float) (page_width / 2 + 50.0f) top1:(float)(page_height / 2 + 50.0f)];
    // Add a new signature to page.
    FSSignature *new_signature = [pdf_page addSignature:new_sig_rect];
    if ([new_signature isEmpty])
        return ;
    NSString *filter = @"Adobe.PPKLite";
    NSString *sub_filter = @"ETSI.CAdES.detached";
    // Set filter and subfilter for the new signature.
    [new_signature setFilter:filter];
    [new_signature setSubFilter:sub_filter];
    // Set values for the new signature.
    [new_signature setKeyValue:FSSignatureKeyNameSigner value:@"Foxit PDF SDK"];
    [new_signature setKeyValue:FSSignatureKeyNameContactInfo value:@"support@foxitsoftware.com"];
    [new_signature setKeyValue:FSSignatureKeyNameDN value:@"CN=CN,MAIL=MAIL@MAIL.COM"];
    [new_signature setKeyValue:FSSignatureKeyNameLocation value:@"Fuzhou, China"];
    NSString *new_value;
    new_value = [NSString stringWithFormat:@"As a sample for subfilter \"%@\", in %@", sub_filter, TransformLevel2NSString(pades_level)];
    [new_signature setKeyValue:FSSignatureKeyNameReason value:new_value];
    [new_signature setKeyValue:FSSignatureKeyNameText value:new_value];
    FSDateTime *sign_time = GetLocalDateTime();
    [new_signature setSignTime:sign_time];
    
    // Set appearance flags to decide which content would be used in appearance.
    uint32 ap_flags = FSSignatureAPFlagLabel | FSSignatureAPFlagSigner |
    FSSignatureAPFlagReason | FSSignatureAPFlagDN |
    FSSignatureAPFlagLocation | FSSignatureAPFlagText |
    FSSignatureAPFlagSigningTime;
    [new_signature setAppearanceFlags:ap_flags];
    
    // Sign the new signature.
    NSString *cert_file_path = [input_path stringByAppendingPathComponent:@"foxit_all.pfx"];
    NSString *cert_file_password = @"123456";
    FSProgressive* sign_progressive = [new_signature startSign:cert_file_path cert_password:cert_file_password digest_algorithm:FSSignatureDigestSHA256 save_path:cached_signed_pdf_path client_data:nil pause:nil];
    if (100 != [sign_progressive getRateOfProgress]) {
        if (FSProgressiveFinished == [sign_progressive resume]) {
            NSLog(@"[Failed] Fail to sign the new CAdES signature.");
            return;
        }
    }
    
    if (pades_level > FSSignaturePAdESLevelBT) {
        FSPDFDoc *cache_pdf_doc = [[FSPDFDoc alloc] initWithPath:cached_signed_pdf_path];
        [cache_pdf_doc startLoad:nil is_cache_stream:NO pause:nil];
        // Here, we only simply create an empty DSS object in PDF document, just as a simple exmaple.
        // In fact, user should use FSLTVVerifier to add DSS.
        [cache_pdf_doc createDSS];
        
        if (pades_level > FSSignaturePAdESLevelBLT) {
            FSPDFPage *cache_pdf_page = [cache_pdf_doc getPage:0];
            FSRectF* empty_rect = [[FSRectF alloc] init];
            FSSignature* timestamp_signature = [cache_pdf_page addSignatureWithSignatureType:empty_rect field_name:@"" signature_type:FSSignatureSignatureTypeTimeStamp to_check_permission:YES];
            FSProgressive* sign_ts_progressive = [timestamp_signature startSign:@"" cert_password:@"" digest_algorithm:FSSignatureDigestSHA256 save_path:real_signed_pdf_path client_data:nil pause:nil];
            if (100 != [sign_ts_progressive getRateOfProgress]) {
                if (FSProgressiveFinished != [sign_ts_progressive resume]) {
                    NSLog(@"[Failed] Fail to sign the new time stamp signature.");
                    return;
                }
            }

        } else {
            [cache_pdf_doc saveAs:real_signed_pdf_path save_flags: FSPDFDocSaveFlagIncremental];
        }
    }
}

void PAdESVerify(NSString* check_pdf_path, FSSignaturePAdESLevel expect_pades_level) {
    FSPDFDoc *check_pdf_doc = [[FSPDFDoc alloc] initWithPath:check_pdf_path];
    [check_pdf_doc startLoad:nil is_cache_stream:NO pause:nil];
    printf("To verify level of PAdES signature in file %s\r\n", [check_pdf_path UTF8String]);
    
    int sig_count = [check_pdf_doc getSignatureCount];
    if (0 == sig_count)
        printf("No signature in current PDF file.\r\n");
    bool has_cades_signature = false;
    for (int i = 0; i < sig_count; i++) {
        FSSignature* temp_sig = [check_pdf_doc getSignature:i];
        if ([temp_sig isEmpty]) continue;
        BOOL is_signed = [temp_sig isSigned];
        if (NO == is_signed) continue;
        if ([[temp_sig getSubFilter] isEqual:@"ETSI.CAdES.detached"]) {
            has_cades_signature = true;
            // Verify PAdES signature.
            FSProgressive* verify_progressive = [temp_sig startVerify:nil pause:nil];
            if (100 != [verify_progressive getRateOfProgress]) {
                if (FSProgressiveFinished != [verify_progressive resume]) {
                    printf("[Failed] Fail to verify a PAdES signature. Signature index:%d\r\n", i);
                    continue;
                }
            }
            unsigned int sig_state = [temp_sig getState];
            printf("Signature index:%d, a PAdES signature. State after verifying: %s\r\n", i, TransformSignatureStateToString(sig_state).c_str());
            
            // Get PAdES level.
            FSSignaturePAdESLevel actual_level = [temp_sig getPAdESLevel];
            printf("Signature index:%d, PAdES level:%s, %s\r\n",
                   i, [TransformLevel2NSString(actual_level) UTF8String],
                   actual_level == expect_pades_level?"matching expected level." : "NOT match expected level.");
        }
    }
    if (false == has_cades_signature)
        printf("No PAdES signature in current PDF file.\r\n");
}

int main(int argc, const char *argv[]) {
    @autoreleasepool {
        NSString *output_directory = [output_path stringByAppendingPathComponent:@"pades/"];

        NSFileManager *fileManager = [NSFileManager defaultManager];
        BOOL isDir = NO;
        if ([fileManager fileExistsAtPath:output_directory isDirectory:&isDir]) {
            if (!isDir) {
                NSError *error = nil;
                if (![fileManager removeItemAtPath:output_directory error:&error] || error) {
                    return -1;
                }
                if (![fileManager createDirectoryAtPath:output_directory withIntermediateDirectories:YES attributes:nil error:&error] || error) {
                    return -1;
                }
            }
        } else {
            NSError *error = nil;
            if (![fileManager createDirectoryAtPath:output_directory withIntermediateDirectories:YES attributes:nil error:&error] || error) {
                return -1;
            }
        }

        // Initialize library
        FSErrorCode errorCode = [FSLibrary initialize:sn key:key];
        if (errorCode != FSErrSuccess) {
            NSLog(@"Library Initialize Error: %ld", errorCode);
            return -1;
        }

        NSString* input_file_name = @"AboutFoxit.pdf";
        NSString *input_file_path = [input_path stringByAppendingPathComponent:input_file_name];
        printf("Input file path: %s\n", [input_file_path UTF8String]);
        @try {
            [FSTimeStampServerMgr initialize];
	    
            FSSignaturePAdESLevel level = FSSignaturePAdESLevelBB;
            NSString* file_name = [input_file_name substringToIndex:(input_file_name.length-4)];
            NSString* signed_file_name = [NSString stringWithFormat:@"%@%@.pdf",file_name,TransformLevel2NSString(level)];
            NSString* signed_pdf_path = [output_directory stringByAppendingPathComponent:signed_file_name];
            PAdESSign(input_file_path, signed_pdf_path, level);
            PAdESVerify(signed_pdf_path, level);
            
            NSString* free_server_name = @"FreeTSAServer";
            NSString* free_server_url = @"http://ca.signfiles.com/TSAServer.aspx";
            FSTimeStampServer* timestamp_server = [FSTimeStampServerMgr addServer:free_server_name server_url:free_server_url user_name:@"" password:@""];
            [FSTimeStampServerMgr setDefaultServer:timestamp_server];
            for (level = FSSignaturePAdESLevelBT; level <= FSSignaturePAdESLevelBLTA; level = FSSignaturePAdESLevel(level+1)) {
                signed_file_name = [NSString stringWithFormat:@"%@%@.pdf",file_name,TransformLevel2NSString(level)];
                signed_pdf_path = [output_directory stringByAppendingPathComponent:signed_file_name];
                PAdESSign(input_file_path, signed_pdf_path, level);
                PAdESVerify(signed_pdf_path, level);
            }
            
            [FSTimeStampServerMgr destroy];
        }
        @catch (NSException *e) {
            NSLog(@"Unknown Exception, %@", e);
            return -1;
        }
    }
    [FSLibrary destroy];
    return 0;
}
