//
//  BasicFiles.m
//  X-BASIC for iOS
//
//  Created by AIG-Soft on 2012/08/17.
//  Copyright (c) 2012 AIG-Soft. All rights reserved.
//

#import "BasicFiles.h"
#import	"click.h"
#import "libs.h"
#import "main.h"
#import "FileControl2.h"

#include <stdio.h>
#include "code2code.h"
#include "support.h"

@implementation BasicFiles

//-------------------------------------------------------------------

-(void)viewDidLoad
{
	printSelf();
	[super viewDidLoad];

	// デリゲートとソース設定;nib部なのでここに置く
	self.pickerView.delegate  =self;
	self.pickerView.dataSource=self;
	//
	[super setDelButtonAction:self delSelector:@selector(deleteBasicFile)];
	[super setDelButtonTitle:@"DELETE"];
	[super delButtonHidden:NO];
}

//-------------------------------------------------------------------

static
LineNO getSourceLines(UByte *source,int size)
// 行数を数える
{
	nslog(@"getLines:size=%d",size);
	LineNO lines=0;
	UByte c;
	while (size-->0) {
		c=*source;
		switch (c) {
			case CR:
				if (*(source+1)==LF) {
					source++; // CR,LFならLFも飛ばす
					size--;
				}
			case LF:
				lines++; // 行数+1
			default:
				source++;
				break;
		}
	} // while
	// プログラム終端まで来た
	nslog(@"行数=%d",lines);
	return (lines);
}

static
void freeSource(void)
{
	if (sourceTop!=NULL) {
		nslog(@"前のソース用ワークを解放");
		// ソースはmalloc()で確保しているのでfree2()にしない
		free(sourceTop);
	}
}

NSInteger readBasicFile(NSString *nsBasicFile,ULong *startupSize0,LineNO *startLines0)
// ソースコード読みだし
// nsBasicFile : フルパス名 (V1.30)
{
	nslog(@"readBasicFile:%@",nsBasicFile);
	
	char *basicFile;

	// startup対応
	FILE *startupFp;
	int startupSize=0; // なかった時のため
	*startLines0=0;
	if (![[nsBasicFile lastPathComponent]isEqualToString:STARTUP]) {
		NSString *startup=getResourceOrSavedataPath(STARTUP); // 外部 → 内蔵の優先順位
		if (startup==nil) {
			// startupがない
			nslog(@"startupがない:%@",startup);
			// なかったら読み込まないだけでエラーにはしない
		} else {
			nslog(@"スタートアップファイル=%@",startup);
			basicFile=nsStringTocString(startup); // NSStringからUTF8でCの文字列を生成する
			startupFp=fopen(basicFile,"r");
			if (startupFp!=NULL) {
				// startupはUTF8固定
				// コード変換しないのでファイルサイズそのままで読めば良い
				startupSize=filelength(startupFp);
				nslog(@"stratupファイルサイズ=%d",startupSize);
				// startupSize=0もあり得る
				if (startupSize==0) {
					fclose(startupFp);
				}
			} else { // startupFp==NULL
				nslog(@"file can not open (startup)");
				// startupは上で検索されているので、通常読めないはずはないが念のため
				// starupSize=0になってる
			}
		}
	} // startup.basそのものを読み込もうとしているときはその前への読み込みはなし
	
	// ユーザー指定ファイルチェック
	basicFile=nsStringTocString(nsBasicFile); // NSStringからUTF8でCの文字列を生成する
	nslog(@"basicFile=%s",basicFile);
	FILE *fp=fopen(basicFile,"r");
	if (fp==NULL) {
		if (startupSize>0) fclose(startupFp);
		nslog(@"file can not open");
		return(RET_readBasicFile_file_not_found);
	}

	// ソースの形式を認識する
	int fLineNo;
	CODETYPE cType=checkFileSjisUtf8(fp,&fLineNo); // fpは巻き戻される
	nslog(@"ユーザーファイル形式(cType):%d/行番号=%d",cType,fLineNo);

	// ソース中のTABの数を数える
	int tabs=countTabsInFile(fp);
	tabs*=(TABWIDTH-1);
	// TABは桁位置によって変換されるスペース数が異なるが、ワーク確保上は、すべてTABWIDTHに変換されると仮定する
	// 元々TAB分1バイトは計上されているので、−1バイトしておけば良い
	
	// ソース用ワーク(スタートアップを含む)
	// SJIS→UTF8では最大でソースコードは1.5倍になる
	// ASCIIコードは同じ、半角カタカナはと2バイトコードは3バイト(サポート範囲)になる可能性がある
	// なので、とりあえず倍のサイズを確保しておく
	int size=filelength(fp);
	nslog(@"ユーザーファイルサイズ=%d+TAB変換サイズ=%d",size,tabs);
	if (cType==FILE_SJIS) size*=2; // UTF8へのコード変換を伴うときは余裕を持ってワークを確保しておく(後で調整)
	size+=tabs; // それにTABの変換分を入れておく
	
	freeSource(); // 前のソース用ワークを解放
	// ↓ここはfreeAll()の対象外にするためmalloc2()にしない
	sourceTop=malloc(startupSize+size+2); // EOS+偶数境界用
	if (sourceTop==NULL) {
		// メモリー不足
		nslog(@"メモリー不足(1):size=%d",startupSize+size+2);
		if (startupSize>0) fclose(startupFp);
		fclose(fp);
		return (RET_readBasicFile_memory_error);
	}

	UByte *source=sourceTop;
	if (startupSize>0) {
		// startup読み込み
		// コード変換なしなので、そのまま
		// この中は表示されないのでTAB変換もしない
		nslog(@"スタートアップ読み込み");
		fread(source,startupSize,1,startupFp);
		//
		// 行数を数える
		*startLines0=getSourceLines(source,startupSize);
		//
		source+=startupSize;
		if (*(source-1)==EOT) {
			source--; // EOTで終わっているときはそれを消す
			startupSize--;
		}
		if ((ULong)source&1) {
			*source++=' '; // 偶数境界化
			startupSize++;
		}
		fclose(startupFp);
	}
	nslog(@"sourceTop=%p.source=%p,startupSize=%d",sourceTop,source,startupSize);
	
	nslog(@"ユーザーソースファイル読み込み with コード変換など");
	// 結局のところコード変換が必要なのは、強制SHIFT-JISモードでないときのcType=FILE_SJIS時のみ
	if (operationStatus.fsjis) {
		nslog(@"強制SJISモード:");
		if (cType==FILE_SJIS) {
			// ファイルもSJISだった
			nslog(@"続行");
		} else {
			// ファイルはUTF8/ASCIIだった
			operationStatus.fsjis=NO; // 他の文字コードの時は強制SHIFT-JISモード解除
			// ファイルは無変換
			nslog(@"解除");
		}
		cType=FILE_ASCII;	// ファイルはいずれにせよ無変換
	} else {
		nslog(@"通常モード:");
		if (cType==FILE_UTF8) cType=FILE_ASCII;	// UTF8は無変換にする(SJISはUTF8変換)
	}
	// 改行コードはLF１つのみにする(V2.8)
	UByte *bp=loadSourceFileWithCodeConvert(fp,cType,source,fLineNo,LF);
	fclose(fp);

	if (cType==FILE_SJIS) {
		// 必要サイズで確保し直し
		size=startupSize+(int)(bp-source)+2; // EOS+偶数境界用
		nslog(@"読み込み後サイズ=%d",size);
		sourceTop=realloc(sourceTop,size+2);
		if (sourceTop==NULL) { // 減らす方向だからエラーは出ないと思うけど念のため
			// メモリー不足
			nslog(@"メモリー不足(2):size=%d",size);
			return (RET_readBasicFile_memory_error);
		}
	}
//	nslog(@"sourceTop=%s",sourceTop);

	sourceEnd=sourceTop+startupSize+size;
	*sourceEnd++=EOS;
	if ((ULong)sourceEnd&1) *sourceEnd++=EOS; // 偶数アドレス化
	*startupSize0=startupSize;

	nslog(@"source=%p～%p(size=%d+%d)",sourceTop,sourceEnd,startupSize,size);
	// リターン値
	return(RET_readBasicFile_OK); // OK
}

//-------------------------------------------------------------------

-(void)setInternalBasicSource
// 内蔵BASICソース一覧取得
{
	nslog(@"setInternalBasicSource:dir=%@",DIR_SYSTEM);
	
	cntInternal=[_basPathes count]; // ここから内部ファイルが始まる
	
	// ファイル名(*.bas)
	NSArray *fileListInternal=[[NSBundle mainBundle]pathsForResourcesOfType:EXT_BASIC inDirectory:DIR_SYSTEM]; // 本体内部はこれ
//	NSArray *fileListInternal=[files(DIR_SYSTEM,EXT_BASIC,YES,FILES_FILE,NO) retain];
	nslog(@"fileListInternal=%@",fileListInternal);
	//
	NSEnumerator *emu=[fileListInternal objectEnumerator];
	NSString *path;
	while (path=[emu nextObject]) {
		NSString *fname=[path lastPathComponent];
#if 0	// 内蔵と外部の識別ができるようになったので外す
		if ([_basFiles indexOfObject:fname]!=NSNotFound) {
			nslog(@"外部に同名ファイル有り:%@",fname);
			// 登録しない
			continue;
		}
#endif
//		nslog(@"fname=%@(%c)",fname,[fname characterAtIndex:0]); // こっちはファイル名のみ
		nslog(@"fname=%@",fname);
		switch ([fname characterAtIndex:0]) {
			default:
				[_basFiles  addObject:[NSString stringWithFormat:@"*%@",fname]]; // 内蔵ファイルの印をつけておく
				[_basPathes addObject:path]; // 保存
				break;
#if COMPILE_MODE==2 // Distributionの時は外す
			case '!':// 先頭が'!'で始まるファイルは、Distribution時は飛ばす
//				NSLog(@"スキップ対象ファイル名:%@",fname);
#endif
			case '.':// 隠しファイルはスキップする
				break;
		}
	}
	nslog(@"_basFiles=%@",_basFiles);
//	nslog(@"setInternalBasicSource end");
}

-(void)setExternalBasicSource
// iTunes経由BASICソース一覧取得
// こちらが優先
{
	nslog(@"setExternalBasicSource:dir=%@",DIR_DOCUMENTS);

	// ファイル名(*.bas)
//	NSArray *fileListExternal=[[[NSBundle mainBundle]pathsForResourcesOfType:EXT_BASIC inDirectory:DIR_DOCUMENTS]retain]; // iTunes経由はこれ・・・ではだめで↓これ
	NSArray *fileListExternal=files(DIR_DOCUMENTS,EXT_BASIC,YES,FILES_FILE,NO); // ファイルのみ
	nslog(@"fileListExternal=%@",fileListExternal);
	NSEnumerator *emu=[fileListExternal objectEnumerator];
	NSString *path;
	while (path=[emu nextObject]) {
		// 同一パス内に同一ファイル名は存在しないから排除処理なし
//		nslog(@"path=%@ / %@",path,[path lastPathComponent]);
		if ([[path lastPathComponent] characterAtIndex:0]!='.') {
			[_basFiles  addObject:[path lastPathComponent]];
			[_basPathes addObject:path]; // 保存
		} // 隠しファイルはスキップする
	}
	nslog(@"_basFiles=%@",_basFiles);
//	nslog(@"setExternalBasicSource end");
}

//-------------------------------------------------------------------

-(void)makeBasicFileList
// 全BASICソースリストを得る
{
	printSelf();
	_basFiles =[[NSMutableArray alloc]init];
	_basPathes=[[NSMutableArray alloc]init];

	// [0]に新規作成を入れる
	[_basFiles  addObject:NSLocalStr(@"NEW")]; // Create a new / 新規作成
	[_basPathes addObject:PATH_NEWFILE];
	//
	[self setExternalBasicSource]; // iTunes経由(優先)
	[self setInternalBasicSource]; // 内蔵
}

//-------------------------------------------------------------------

-(void)open:(id)retTarget retSelector:(SEL)retSelector basicPath:(NSString *)basicPath calcelSelector:(SEL)cancelSelector
// 前の選択項目にポイントを合わせてpickerViewを開く
{
	printSelf();
	// openの方が先に来る
	nslog(@"basicPath=%p",basicPath);
	if ([basicPath isEqualToString:PATH_NEWFILE]) {
		nslog(@"new_file");
	}
	nslog(@"basicPath=%@",basicPath);
	
	[self makeBasicFileList]; // 全BASICソースリストを得る
	
	// 前回選択していたソースにポイントを合わせる
	// basicFileに相当する要素番号を得る
	NSUInteger no=0;
	NSEnumerator *emu=[_basPathes objectEnumerator];
	nslog(@"_basPathes=%@",_basPathes);
	NSString *fname;
	while (fname=[emu nextObject]) {
		if ([fname isEqualToString:basicPath]) break;
		no++;
	}
	if (fname==nil) no=0; // そのファイル名が見つからない時（ファイルを削除したとき）は、no=0（先頭）にする
	// ↓ここでなぜかnot foundが出るが無視する（実行はされる）
	[super initPickerView:retTarget
			  retSelector:retSelector
					title:NSLocalStr(@"BASICFILES")
					 cols:_basFiles // 項目はファイル名のみ
				selectRow:no
				 btnTitle:NSLocalStr(@"LOAD")
		   cancelSelector:cancelSelector
	 ];
}

//-------------------------------------------------------------------

// ソースファイル削除(V1.3)
// pickerから呼び出される
-(void)deleteBasicFile
{
	printSelf();
	nslog(@"selectRow=%d/cntInternal=%d",self.selectRow,cntInternal);
	//
	NSString *fname=_basFiles[self.selectRow]; // ここはファイル名のみでOK
	NSString *message;
	if (self.selectRow==0||self.selectRow>=cntInternal) {
		// 新規作成/内蔵は削除できない
		message=[NSString stringWithFormat:NSLocalStr(@"CANTDELETE_MESSAGE"),fname];
		Alert1(@"DELETE_CHECK", message, nil);
	} else {
		// 削除確認
		message=[NSString stringWithFormat:NSLocalStr(@"DELETE_MESSAGE"),fname];
		UIAlertView *alertView = [[UIAlertView alloc]  initWithTitle:NSLocalStr(@"DELETE_CHECK")
												message:message
											   delegate:self
									  cancelButtonTitle:NSLocalStr(@"CANCEL")
									  otherButtonTitles:NSLocalStr(@"OK"),nil
					 ];
		// 名前を付けて保存のボタン番号
		clickSound();
		[alertView show];
	}
}

-(void)alertView:(UIAlertView *)alertView_ clickedButtonAtIndex:(NSInteger)buttonIndex
// alertViewのボタンが押された
{
	nslog(@"alertView:ボタン=%d",buttonIndex);
	///	clickSound();
	if (buttonIndex==alertView_.cancelButtonIndex) { // キャンセル
		// alertView自体が画面外タップなどでキャンセルされた時もここに来る
		nslog(@"キャンセル");
	} else {
		nslog(@"ファイル削除");
		fileDelete(_basPathes[self.selectRow]); // 削除はフルパス名で行う
		//
		nslog(@"ファイルリスト再作成");
		[self makeBasicFileList]; // 全BASICソースリストを得る
		// selectRowは通常動かさなくて良い(削除された部分のみ消える)
		if (self.selectRow>=[_basFiles count]) {
			// しかし、最後のファイルを消したときは1つ上に上げる
			// 実際には内蔵ソースがあるためここには来ないはず
			self.selectRow=[_basFiles count]-1;
		}
		[super reSetPickerView:_basFiles selectRow:self.selectRow];
	}
}

//-------------------------------------------------------------------

#pragma mark -
#pragma mark Memory management

#ifdef DEBUG
- (void)didReceiveMemoryWarning
{
	printSelf();
	[super didReceiveMemoryWarning];
}

#endif

- (void)dealloc
{
	printSelf();
	_fclosePopover=NO;
}

@end
