/* MZ-700 MZT/MZF single-record file support.
 * The two extensions describe the same 128-byte-header byte stream. */
#define B2026_MZT_HEADER_SIZE 128U
#define B2026_MZT_MAX_DATA 65535U
#define B2026_MZT_60K 61440U

static void b2026_mzt_pair_num(const char *target,const char *jp,const char *en,double value){
    if(target==NULL||*target=='\0')return;hash_set_number(target,jp,value);hash_set_number(target,en,value);
}
static void b2026_mzt_pair_text(const char *target,const char *jp,const char *en,const char *value){
    if(target==NULL||*target=='\0')return;hash_set_string(target,jp,value!=NULL?value:"");hash_set_string(target,en,value!=NULL?value:"");
}
static void b2026_mzt_clear_info(const char *target){
    if(target!=NULL&&*target!='\0'){HashVariable*h=ensure_local_hash(get_hash_frame(target),target);clear_hash_entries(h);}
}
static void b2026_mzt_failed_info(const char *target,const char *path){
    b2026_mzt_clear_info(target);b2026_mzt_pair_num(target,"成功","ok",0);b2026_mzt_pair_text(target,"状態","status","FAILED");
    b2026_mzt_pair_text(target,"ファイル","file",path!=NULL?path:"");
}
static void b2026_mzt_log_error(int code,const char *operation,const char *log_path,const char *path,const char *detail){
    char message[4096];snprintf(message,sizeof(message),"%s failed: path=%s: %s / 【MZT/MZFエラー】%s",
        operation,path!=NULL?path:"",detail!=NULL?detail:"unknown error",detail!=NULL?detail:"処理できません");
    basic_set_error_logged(code,message,operation,current_source_path,current_line,"MZT");b2026_log_message_grouped(log_path,message);
}
static void b2026_mzt_log_warning(const char *operation,const char *log_path,const char *path,const char *detail){
    char message[4096];snprintf(message,sizeof(message),"%s warning: path=%s: %s / 【MZT/MZF注意】%s",
        operation,path!=NULL?path:"",detail!=NULL?detail:"warning",detail!=NULL?detail:"注意事項があります");
    b2026_log_message_grouped(log_path,message);
}
static int b2026_mzt_ascii_equal(const char*a,const char*b){
    unsigned char ca,cb;if(a==NULL||b==NULL)return 0;while(*a&&*b){ca=(unsigned char)*a++;cb=(unsigned char)*b++;
        if(ca>='a'&&ca<='z')ca=(unsigned char)(ca-32);if(cb>='a'&&cb<='z')cb=(unsigned char)(cb-32);if(ca!=cb)return 0;}return *a=='\0'&&*b=='\0';
}
static const char *b2026_mzt_extension(const char *path){
    const char *slash,*backslash,*base,*dot;if(path==NULL)return NULL;slash=strrchr(path,'/');backslash=strrchr(path,'\\');
    if(backslash!=NULL&&(slash==NULL||backslash>slash))slash=backslash;base=slash!=NULL?slash+1:path;dot=strrchr(base,'.');return dot;
}
static int b2026_mzt_valid_extension(const char *path){
    const char *ext=b2026_mzt_extension(path);return ext!=NULL&&(b2026_mzt_ascii_equal(ext,".mzt")||b2026_mzt_ascii_equal(ext,".mzf"));
}
static const char *b2026_mzt_format_from_path(const char *path){
    const char *ext=b2026_mzt_extension(path);return ext!=NULL&&b2026_mzt_ascii_equal(ext,".mzf")?"MZF":"MZT";
}
static char *b2026_mzt_output_path(const char *path,int alias_mzf){
    const char *ext;char *copy,*result;size_t n,suffix_n;if(path==NULL||*path=='\0')return NULL;ext=b2026_mzt_extension(path);
    if(ext!=NULL&&!b2026_mzt_valid_extension(path))return NULL;if(ext!=NULL)return resolve_filesystem_path_runtime(path,1);
    n=strlen(path);suffix_n=4U;copy=(char*)malloc(n+suffix_n+1U);if(copy==NULL)return NULL;memcpy(copy,path,n);memcpy(copy+n,alias_mzf?".mzf":".mzt",suffix_n+1U);
    result=resolve_filesystem_path_runtime(copy,1);free(copy);return result;
}
static int b2026_mzt_valid_internal_char(unsigned char c){
    return (c>='A'&&c<='Z')||(c>='a'&&c<='z')||(c>='0'&&c<='9')||c==' '||c=='_'||c=='-'||c=='.';
}
static int b2026_mzt_make_internal_name(const char *source,unsigned char out[16],size_t *length_out){
    size_t i,n;if(source==NULL)return 0;n=strlen(source);if(n<1U||n>16U)return 0;
    for(i=0;i<n;++i){unsigned char c=(unsigned char)source[i];if(!b2026_mzt_valid_internal_char(c))return 0;if(c>='a'&&c<='z')c=(unsigned char)(c-32);out[i]=c;}
    *length_out=n;return 1;
}
static int b2026_mzt_integer_address(double value,unsigned int *out){
    if(!isfinite(value)||floor(value)!=value||value<0.0||value>65535.0)return 0;*out=(unsigned int)value;return 1;
}
static int b2026_mzt_verify_file(const char *path,const unsigned char header[128],const unsigned char *payload,size_t payload_n){
    FILE *fp;unsigned char actual[128],buffer[4096];size_t offset=0U,n;int extra,ok=0;fp=b2026_fopen(path,"rb");if(fp==NULL)return 0;
    if(fread(actual,1U,128U,fp)!=128U||memcmp(actual,header,128U)!=0)goto done;
    while(offset<payload_n){n=payload_n-offset;if(n>sizeof(buffer))n=sizeof(buffer);if(fread(buffer,1U,n,fp)!=n||memcmp(buffer,payload+offset,n)!=0)goto done;offset+=n;}
    extra=fgetc(fp);if(extra!=EOF||ferror(fp))goto done;ok=1;
done:fclose(fp);return ok;
}

static const char *b2026_save_mzt(const char *path,const char *array_name,const char *internal_name,double load_value,double exec_value,int alias_mzf){
    ArrayVariable *array_var;unsigned char header[128],name_bytes[16],*payload=NULL;size_t count,i,name_n=0U;unsigned int load_address,exec_address;
    char *output=NULL,*stage=NULL,detail[512];FILE *fp=NULL;int ok=0;const char *operation=alias_mzf?"SAVEMZF":"SAVEMZT";basic_clear_error();
    output=b2026_mzt_output_path(path,alias_mzf);if(output==NULL){b2026_mzt_log_error(4701,operation,"__SAVEMZT_ERROR.log",path,"invalid or unsupported output path; use .mzt or .mzf / 保存先が空か拡張子が.mzt/.mzfではありません");goto done;}
    array_var=get_array_variable(array_name);if(array_var==NULL||array_var->data==NULL||array_var->size<option_base){b2026_mzt_log_error(4702,operation,"__SAVEMZT_ERROR.log",output,"data array is empty or does not exist / データ配列が空か存在しません");goto done;}
    count=(size_t)(array_var->size-option_base+1);if(count<1U||count>B2026_MZT_MAX_DATA){b2026_mzt_log_error(4703,operation,"__SAVEMZT_ERROR.log",output,"data length must be 1..65535 bytes / データ長は1～65535バイトで指定してください");goto done;}
    if(!b2026_mzt_integer_address(load_value,&load_address)||!b2026_mzt_integer_address(exec_value,&exec_address)){b2026_mzt_log_error(4704,operation,"__SAVEMZT_ERROR.log",output,"load and execution addresses must be integers from 0 to 65535 / ロード・実行アドレスは0～65535の整数で指定してください");goto done;}
    if((size_t)load_address+count>65536U){b2026_mzt_log_error(4705,operation,"__SAVEMZT_ERROR.log",output,"load address plus data length exceeds the 64KiB address space / ロードアドレスとデータ長の合計が64KiBを超えています");goto done;}
    if(!b2026_mzt_make_internal_name(internal_name,name_bytes,&name_n)){b2026_mzt_log_error(4706,operation,"__SAVEMZT_ERROR.log",output,"internal name must be 1..16 ASCII characters using A-Z, 0-9, space, _, -, or . / 内部ファイル名は指定可能文字による1～16バイトで指定してください");goto done;}
    payload=(unsigned char*)malloc(count);if(payload==NULL){b2026_mzt_log_error(4707,operation,"__SAVEMZT_ERROR.log",output,"out of memory / メモリ不足です");goto done;}
    for(i=0;i<count;++i){B2026_Var *slot=&array_var->data[option_base+(int)i];double v;if(b2026_var_is_string(slot)||b2026_var_is_null(slot)){b2026_mzt_log_error(4708,operation,"__SAVEMZT_ERROR.log",output,"data array contains a string or NULL element / データ配列に文字列またはNULL要素があります");goto done;}v=b2026_var_as_number(slot);if(!isfinite(v)||floor(v)!=v||v<0.0||v>255.0){b2026_mzt_log_error(4708,operation,"__SAVEMZT_ERROR.log",output,"every data element must be an integer from 0 to 255 / 全データ要素は0～255の整数で指定してください");goto done;}payload[i]=(unsigned char)v;}
    memset(header,0,sizeof(header));header[0]=1U;memset(header+1,' ',17U);memcpy(header+1,name_bytes,name_n);header[1U+name_n]=0x0DU;
    header[0x12]=(unsigned char)(count&255U);header[0x13]=(unsigned char)((count>>8)&255U);header[0x14]=(unsigned char)(load_address&255U);header[0x15]=(unsigned char)(load_address>>8);header[0x16]=(unsigned char)(exec_address&255U);header[0x17]=(unsigned char)(exec_address>>8);
    if(!saveimage_ensure_directories_safe(output,detail,sizeof(detail))){b2026_mzt_log_error(4709,operation,"__SAVEMZT_ERROR.log",output,detail[0]?detail:"output directory could not be created / 保存先ディレクトリを作成できません");goto done;}
    stage=b2026_atomic_temp_path(output);if(stage==NULL||(fp=b2026_fopen(stage,"wb"))==NULL){b2026_mzt_log_error(4710,operation,"__SAVEMZT_ERROR.log",output,"temporary output could not be opened / 一時出力ファイルを開けません");goto done;}
    if(fwrite(header,1U,sizeof(header),fp)!=sizeof(header)||fwrite(payload,1U,count,fp)!=count||fflush(fp)!=0||ferror(fp)){b2026_mzt_log_error(4711,operation,"__SAVEMZT_ERROR.log",output,"file write failed / ファイル書き込みに失敗しました");goto done;}
    if(fclose(fp)!=0){fp=NULL;b2026_mzt_log_error(4711,operation,"__SAVEMZT_ERROR.log",output,"file close failed / ファイル終了処理に失敗しました");goto done;}fp=NULL;
    if(!b2026_mzt_verify_file(stage,header,payload,count)){b2026_mzt_log_error(4712,operation,"__SAVEMZT_ERROR.log",output,"post-write verification failed / 保存後の検証に失敗しました");goto done;}
    if(!b2026_atomic_commit_file(stage,output)){b2026_mzt_log_error(4713,operation,"__SAVEMZT_ERROR.log",output,"verified file could not be committed / 検証済みファイルを保存先へ反映できません");goto done;}free(stage);stage=NULL;ok=1;
    if(count>B2026_MZT_60K)b2026_mzt_log_warning(operation,"__SAVEMZT_ERROR.log",output,"data length exceeds 60KiB; ordinary MZ-700 loading may not fit / データ長が60KiBを超えています");
done:if(fp!=NULL)fclose(fp);if(!ok&&stage!=NULL)b2026_atomic_discard_file(stage);free(stage);free(payload);
    if(ok){const char *result=runtime_temp_string_copy(output);free(output);return result;}free(output);return runtime_temp_string_copy("");
}

static int b2026_mzt_replace_array(const char *array_name,const unsigned char *payload,size_t count){
    ArrayVariable *target;B2026_Var *new_data;int *new_ref;int upper;size_t alloc_count,i;
    upper=option_base+(int)count-1;alloc_count=upper>=0?(size_t)upper+1U:1U;new_data=(B2026_Var*)calloc(alloc_count,sizeof(B2026_Var));if(new_data==NULL)return 0;
    new_ref=(int*)malloc(sizeof(int));if(new_ref==NULL){free(new_data);return 0;}*new_ref=1;for(i=0;i<count;++i)set_b2026_number(&new_data[option_base+(int)i],(double)payload[i]);
    target=ensure_local_array(get_array_frame(array_name),array_name);release_array_storage(target);target->data=new_data;target->ref_count_ptr=new_ref;target->size=upper;target->read_count=(int)count;target->bypass_cow=0;return 1;
}
static int b2026_load_mzt(const char *target,const char *path,const char *array_name,int alias_mzf){
    char *input=NULL;FILE *fp=NULL;unsigned char header[128],*payload=NULL;long file_size;size_t count,name_n=0U,i;unsigned int load_address,exec_address,end_address;int reserved_nonzero=0,ok=0;char internal[17];
    const char *operation=alias_mzf?"LOADMZF":"LOADMZT";basic_clear_error();b2026_mzt_failed_info(target,path);
    if(path==NULL||*path=='\0'||!b2026_mzt_valid_extension(path)){b2026_mzt_log_error(4720,operation,"__LOADMZT_ERROR.log",path,"input path must end in .mzt or .mzf / 入力パスは.mztまたは.mzfで指定してください");goto done;}
    input=resolve_filesystem_path_runtime(path,0);if(input==NULL||(fp=b2026_fopen(input,"rb"))==NULL){b2026_mzt_log_error(4721,operation,"__LOADMZT_ERROR.log",path,"file does not exist or cannot be opened / ファイルが存在しないか開けません");goto done;}
    if(fseek(fp,0,SEEK_END)!=0||(file_size=ftell(fp))<0||fseek(fp,0,SEEK_SET)!=0){b2026_mzt_log_error(4722,operation,"__LOADMZT_ERROR.log",input,"file size could not be read / ファイルサイズを取得できません");goto done;}
    if(file_size<129L||fread(header,1U,sizeof(header),fp)!=sizeof(header)){b2026_mzt_log_error(4723,operation,"__LOADMZT_ERROR.log",input,"header is missing or truncated / ヘッダーがないか途中で切れています");goto done;}
    if(header[0]!=1U){b2026_mzt_log_error(4724,operation,"__LOADMZT_ERROR.log",input,"unsupported attribute; only machine-language attribute 0x01 is supported / 属性が0x01ではないため未対応です");goto done;}
    for(name_n=0U;name_n<17U&&header[1U+name_n]!=0x0DU;++name_n){}
    if(name_n<1U||name_n>16U){b2026_mzt_log_error(4725,operation,"__LOADMZT_ERROR.log",input,"internal filename terminator is invalid / 内部ファイル名の終端が正しくありません");goto done;}
    for(i=name_n+1U;i<17U;++i)if(header[1U+i]!=(unsigned char)' '){b2026_mzt_log_error(4725,operation,"__LOADMZT_ERROR.log",input,"internal filename padding is invalid / 内部ファイル名の余白が正しくありません");goto done;}
    memcpy(internal,header+1,name_n);internal[name_n]='\0';count=(size_t)header[0x12]|((size_t)header[0x13]<<8);load_address=(unsigned int)header[0x14]|((unsigned int)header[0x15]<<8);exec_address=(unsigned int)header[0x16]|((unsigned int)header[0x17]<<8);
    if(count<1U||count>B2026_MZT_MAX_DATA){b2026_mzt_log_error(4726,operation,"__LOADMZT_ERROR.log",input,"data length must be 1..65535 bytes / データ長が1～65535バイトではありません");goto done;}
    if((unsigned long)file_size!=(unsigned long)(B2026_MZT_HEADER_SIZE+count)){b2026_mzt_log_error(4727,operation,"__LOADMZT_ERROR.log",input,"file size does not exactly match one MZT/MZF record / ファイルサイズが単一レコードの長さと一致しません");goto done;}
    if((size_t)load_address+count>65536U){b2026_mzt_log_error(4728,operation,"__LOADMZT_ERROR.log",input,"load address plus data length exceeds the 64KiB address space / ロードアドレスとデータ長の合計が64KiBを超えています");goto done;}
    for(i=0x18U;i<0x80U;++i)if(header[i]!=0U){reserved_nonzero=1;break;}
    payload=(unsigned char*)malloc(count);if(payload==NULL){b2026_mzt_log_error(4729,operation,"__LOADMZT_ERROR.log",input,"out of memory / メモリ不足です");goto done;}
    if(fread(payload,1U,count,fp)!=count||fgetc(fp)!=EOF||ferror(fp)){b2026_mzt_log_error(4730,operation,"__LOADMZT_ERROR.log",input,"payload is truncated or has trailing data / データが途中で切れているか末尾に余剰データがあります");goto done;}
    if(!b2026_mzt_replace_array(array_name,payload,count)){b2026_mzt_log_error(4729,operation,"__LOADMZT_ERROR.log",input,"out of memory while preparing the destination array / 読込先配列の準備中にメモリ不足になりました");goto done;}
    end_address=load_address+(unsigned int)count-1U;b2026_mzt_clear_info(target);b2026_mzt_pair_num(target,"成功","ok",1);b2026_mzt_pair_text(target,"状態","status","OK");
    b2026_mzt_pair_text(target,"ファイル","file",input);b2026_mzt_pair_text(target,"形式","format",b2026_mzt_format_from_path(input));b2026_mzt_pair_num(target,"属性","attribute",1);
    b2026_mzt_pair_text(target,"内部ファイル名","internalName",internal);b2026_mzt_pair_num(target,"データ長","dataLength",(double)count);b2026_mzt_pair_num(target,"ロードアドレス","loadAddress",(double)load_address);
    b2026_mzt_pair_num(target,"終了アドレス","endAddress",(double)end_address);b2026_mzt_pair_num(target,"実行アドレス","executionAddress",(double)exec_address);b2026_mzt_pair_num(target,"予約領域非ゼロ","reservedNonZero",reserved_nonzero?1:0);b2026_mzt_pair_num(target,"60KiB超過","exceeds60KiB",count>B2026_MZT_60K?1:0);ok=1;
    if(count>B2026_MZT_60K)b2026_mzt_log_warning(operation,"__LOADMZT_ERROR.log",input,"data length exceeds 60KiB; ordinary MZ-700 loading may not fit / データ長が60KiBを超えています");
done:if(fp!=NULL)fclose(fp);free(payload);free(input);return ok;
}
