/********************************************************************/
/* Copyright (c) 2019 System fugen G.K. and Yuzi Mizuno          */
/* All rights reserved.                                             */
/********************************************************************/
/********************************************************************/
#include "stdafx.h"
#include "resource.h"
#include "IO/GLNasImporter.h"
#include "mg/Group.h"
#include "mg/MGStl.h"

#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif

namespace
{
	// Nastran フォーマットのフィールドタイプ
	enum FieldType
	{
		SMALLFIELD, LARGEFIELD, FREEFIELD
	};

	// ファイルストリームのシークポイント復元器
	struct SeekPtrSaver
	{
		std::istream& m_is;
		std::istream::pos_type m_pos;
		
		SeekPtrSaver(std::istream& is)
			 : m_is(is),
			   m_pos(is.tellg())
		{
			ASSERT(is);
		}

		~SeekPtrSaver()
		{
			m_is.clear();
			m_is.seekg(m_pos, std::ios_base::beg);
			ASSERT(m_is);
		}
	};

	// フィールドタイプを調べる
	FieldType DetectFieldType(std::ifstream& fin)
	{
		ASSERT(fin);

		FieldType type = SMALLFIELD;
		SeekPtrSaver raii(fin);
		fin.seekg(0, std::ios_base::beg);

		std::string strLine;
		while(std::getline(fin, strLine)){
			// GRID レコードを一つ調べればよい
			if(strLine.find("GRID") != 0){ // startswith
				continue;
			}

			// CSV then free
			if(strLine.find(',') != std::string::npos){
				type = FREEFIELD;
				break;
			}
			// 'GRID*' means this line and the next line forms one GRID record
			else if(strLine[4] == '*'){
				type = LARGEFIELD;
				break;
			}
			// commonly used format
			else{
				type = SMALLFIELD;
				break;
			}
		} // getline

		return type;
	}

	// $---1--][---2--][---3--][---4--][---5--][---6--][---7--][---8--][---9--][--10--]
	// GRID    ID      CP      X1      X2      X3      CD      PS      SEID
	int HandleSmallField(const std::string& strLine, std::vector<MGPosition>& grids)
	{
		ASSERT(strLine.find("GRID") == 0);

		const int nLen = 8;
		const int id = atoi(strLine.substr(8, nLen).c_str());

		MGPosition pos(
			atof(strLine.substr(24, nLen).c_str()), // X
			atof(strLine.substr(32, nLen).c_str()), // Y
			atof(strLine.substr(40, nLen).c_str())); // Z

		// GRID を一個挿入
		grids.push_back(pos);
		return id;
	}

	// 0       8               24              40              56              72
	// $---1--][-------2------][-------3------][-------4------][-------5------][---6--]
	// GRID*                123               4            3.25            4.56
	// 80      88              104             120             136             152
	// $---1--][-------2------][-------3------][-------4------][-------5------][---6--]
	//                       2.               8
	int HandleLargeField(const std::string& strLine, std::vector<MGPosition>& grids)
	{
		ASSERT(strLine.find("GRID*") == 0);
		// strLine を既に次の行と連結済みとする

		const int nLen = 16;
		const int id = atoi(strLine.substr(8, nLen).c_str());

		MGPosition pos(
			atof(strLine.substr(40, nLen).c_str()), // X
			atof(strLine.substr(56, nLen).c_str()), // Y
			atof(strLine.substr(88, nLen).c_str())); // Z

		// GRID を一個挿入
		grids.push_back(pos);
		return id;
	}

	// ファイル内のすべての GRID レコードを取得する。
	void GetAllGrids(
		FieldType type,
		std::ifstream& fin,
		std::vector<MGPosition>& grids,
		std::map<int, int>& ids)
	{
		ASSERT(fin);
		SeekPtrSaver raii(fin);
		fin.seekg(0, std::ios_base::beg);

		std::vector<MGPosition> gridsWk;
		std::map<int, int> idsWk;

		int nCount = 0;
		int nGID = 0;
		std::string strLine;
		while(std::getline(fin, strLine)){
			if(strLine.find("GRID") != 0){ // startswith
				continue;
			}

			switch(type){
			case SMALLFIELD:
				// 簡単かつ最も一般的な形式 (8 columns * 10)
				nGID = HandleSmallField(strLine, gridsWk);
				idsWk.insert(std::make_pair(nGID, nCount));
				++nCount;
				break;
			case LARGEFIELD:
				// 二行にまたがる
				{
					std::string strLine2;
					std::getline(fin, strLine2);
					if(strLine2.empty()){
						// 見逃す
						break;
					}
					strLine += strLine2;
					nGID = HandleLargeField(strLine, gridsWk);
					idsWk.insert(std::make_pair(nGID, nCount));
					++nCount;
				}
				break;
			case FREEFIELD:
				// not implemented
				break;
			}
		} // getline

		gridsWk.swap(grids);
		idsWk.swap(ids);
	}

	// CTRIA3 レコードを一つ得る
	void HandleCTRIA3(std::string& strLine, std::vector<std::vector<int> >& records)
	{
		if(strLine.length() < 80){
			strLine.insert(strLine.end(), 80 - strLine.length(), ' ');
		}

		//const int nElemId = atoi(strLine.substr(8, 8).c_str());
		const int nVertId[3] = {
			atoi(strLine.substr(24, 8).c_str()),
			atoi(strLine.substr(32, 8).c_str()),
			atoi(strLine.substr(40, 8).c_str()),
		};

		records.push_back(std::vector<int>(nVertId, nVertId + 3));
	}

	// CQUAD4 レコードから CTRIA3 レコードを二つ得る
	void HandleCQUAD4(std::string& strLine, std::vector<std::vector<int> >& records)
	{
		if(strLine.length() < 80){
			strLine.insert(strLine.end(), 80 - strLine.length(), ' ');
		}

		//const int nElemId = atoi(strLine.substr(8, 8).c_str());
		const int nVertId[4] = {
			atoi(strLine.substr(24, 8).c_str()),
			atoi(strLine.substr(32, 8).c_str()),
			atoi(strLine.substr(40, 8).c_str()),
			atoi(strLine.substr(48, 8).c_str()),
		};

		records.push_back(std::vector<int>(nVertId, nVertId + 4));
	}

	bool LoadOnlyTriangles(const CString& strFilePath, MGStl& stl)
	{
		std::ifstream fin(strFilePath);
		if(!fin){
			return false;
		}

		const FieldType type = DetectFieldType(fin);

		// XYZ データを全取得
		// GRID が出現する順序を後で利用するので、それも得る
		std::vector<MGPosition> grids; // Line# -> XYZ
		std::map<int, int> ids; // GRIDID -> Line#
		GetAllGrids(type, fin, grids, ids);
		if(grids.empty()){
			return false;
		}

		std::vector<std::vector<int> > records;
		std::string strLine;
		while(std::getline(fin, strLine)){
			if(strLine.find("CQUAD4") == 0){
				HandleCQUAD4(strLine, records);
			}
			else if(strLine.find("CTRIA3") == 0){
				HandleCTRIA3(strLine, records);
			}
		}
		if(records.empty()){
			return false;
		}

		CString str, strMsg;
		strMsg.Format(IDS_MESH_CELLS_AND_POINTS, records.size(), grids.size());
		str.Format(IDS_INFORMATION, strMsg);
		COUT << (TCAST)str << std::endl;

		// stl にセット
		// grids はそのまま利用できるが、
		// records は番号を付け替えたり、CQUAD4 を分割したりする必要がある。

		std::vector<int> indices; // MGStl::m_indices に相当
		size_t nRec = records.size();
		for(size_t i = 0; i < nRec; ++i){
			std::vector<int>& currec = records[i];

			// 番号を置換
			size_t nVertex = currec.size();
			for(size_t j = 0; j < nVertex; ++j){
				const std::map<int, int>::iterator it = ids.find(currec[j]);
				if(it != ids.end()){
					currec[j] = it->second;
				}
				else{
					return false;
				}
			}

			switch(nVertex){
			case 3: // CTRIA3
				indices.push_back(currec[0]);
				indices.push_back(currec[1]);
				indices.push_back(currec[2]);
				break;
			case 4: // CQUAD4
				indices.push_back(currec[0]);
				indices.push_back(currec[1]);
				indices.push_back(currec[2]);

				indices.push_back(currec[2]);
				indices.push_back(currec[3]);
				indices.push_back(currec[0]);
				break;
			}
		}

		if(indices.empty()){
			return false;
		}

		stl.set_all_data(grids, indices);

		return true;
	}
} // namespace

CGLNasImporter::CGLNasImporter()
	 : CGLFileImporter(IDS_FILTER_NAS)
{
}

bool CGLNasImporter::Load(MGGroup& group)
{
	const CString& strFileName = GetFileName();

	std::unique_ptr<MGStl> spStl(new MGStl);
	if(!LoadOnlyTriangles(strFileName, *spStl)){
		CString str, strMsg;
		strMsg.Format(IDS_TRIANGLE_NOT_EXIST, strFileName);
		str.Format(IDS_ERROR, strMsg);
		COUT << (TCAST)str << std::endl;
		return false;
	}

	group.append(spStl.release());
	return true;
}
