using System;
using System.Collections.Generic;
using System.Data;
using System.IO;
using System.Text;
using DFEngine.Logging;
namespace DFEngine.IO
{
///
/// Class to load and access Shine tables.
///
public class ShineTable : IDisposable
{
///
/// Represents a comment.
///
private const string COMMENT = ";";
///
/// Represents a replacement in the file.
///
private const string REPLACE = "#exchange";
///
/// Represents an ignored part of the file.
///
private const string IGNORE = "#ignore";
///
/// Represents the file's start of definitions.
///
private const string DEFINE = "#define";
///
/// Represents the file's end of definitions.
///
private const string ENDDEFINE = "#enddefine";
///
/// Represents a table indicator.
///
private const string TABLE = "#table";
///
/// Represents the name of a column.
///
private const string COLUMN_NAME = "#columnname";
///
/// Represents the type of a column.
///
private const string COLUMN_TYPE = "#columntype";
///
/// Represents a record in the file.
///
private const string RECORD = "#record";
///
/// Represents a record in a table.
///
private const string RECORD_IN = "#recordin";
///
/// The name of the file we're reading from.
///
private readonly string fileName;
///
/// All of the tables in the file handle.
///
private readonly Dictionary tables;
///
/// Creates a new instance of the class.
///
/// The path of the file.
public ShineTable(string path)
{
fileName = path;
tables = new Dictionary();
PopulateTables();
}
///
/// Returns the table with the associated table name.
///
/// The name of the table.
public DataTable this[string tableName] => tables.GetSafe(tableName);
///
/// Disposes the file handle.
///
public void Dispose()
{
foreach (var table in tables.Values)
{
table.Dispose();
}
tables.Clear();
}
///
/// Checks a value for replacements.
///
private string CheckValue(Tuple replacing, string input)
{
return replacing != null ? input.Replace(replacing.Item1, replacing.Item2) : input;
}
///
/// Converts the input string to a readable string.
///
private string ConvertString(string input)
{
if (input.StartsWith("\\x"))
{
return ((char)Convert.ToByte(input.Substring(2), 16)).ToString();
}
if (input.StartsWith("\\o"))
{
return ((char)Convert.ToByte(input.Substring(2), 8)).ToString();
}
return input.Length > 0 ? input[0].ToString() : "";
}
///
/// Gets the data type for a column.
///
/// The name of the column's data type.
private Type GetColumnDataType(string type)
{
var typeName = type.ToLower();
if (typeName.StartsWith("string["))
{
return typeof(string);
}
switch (typeName)
{
case "byte":
return typeof(byte);
case "word":
return typeof(short);
case "":
case "dwrd":
case "dword":
return typeof(int);
case "qword":
return typeof(long);
case "index":
return typeof(string);
case "":
case "string":
return typeof(string);
}
return typeof(string);
}
///
/// Populates the file's tables with data from the file.
///
private void PopulateTables()
{
if (!File.Exists(fileName))
{
EngineLog.Write(EngineLogLevel.Warning, $"The ShineTable file '{fileName}' could not be found.");
return;
}
using (var file = File.Open(fileName, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
using (var reader = new StreamReader(file, Encoding.Default))
{
var definingStruct = false;
var definingTable = false;
var lineNumber = 0;
DataTable currentTable = null;
List columnTypes = null;
var rowComment = "";
char? ignoreThis = null;
Tuple replaceThis = null;
var unkColumnCount = 0;
while (!reader.EndOfStream)
{
lineNumber++;
string line;
if ((line = reader.ReadLine()) == null)
{
break;
}
if (line.Contains(COMMENT))
{
var index = line.IndexOf(COMMENT, StringComparison.Ordinal);
rowComment = line.Substring(index + 1);
line = line.Remove(index);
}
if (ignoreThis.HasValue)
{
line = line.Replace(ignoreThis.Value.ToString(), "");
}
if (line == string.Empty)
{
continue;
}
var lineLower = line.ToLower();
var items = line.Split('\t');
if (lineLower.StartsWith(REPLACE))
{
replaceThis = new Tuple(ConvertString(items[0]), ConvertString(items[2]));
}
if (lineLower.StartsWith(IGNORE))
{
ignoreThis = ConvertString(items[1])[0];
}
if (lineLower.StartsWith(DEFINE))
{
if (definingStruct || definingTable)
{
throw new Exception("Something is already being defined.");
}
var name = line.Substring(DEFINE.Length + 1);
currentTable = new DataTable(name);
definingStruct = true;
continue;
}
if (lineLower.StartsWith(TABLE))
{
if (definingStruct)
{
throw new Exception("Something is already being defined.");
}
var name = items[1].Trim();
currentTable = new DataTable(name);
columnTypes = new List();
unkColumnCount = 0;
tables.Add(name, currentTable);
definingTable = true;
continue;
}
if (lineLower.StartsWith(ENDDEFINE))
{
if (!definingStruct)
{
throw new Exception("A structure hasn't started being defined.");
}
tables.Add(currentTable.TableName, currentTable);
definingStruct = false;
continue;
}
lineLower = lineLower.Trim();
if (definingStruct)
{
var columnName = rowComment.Trim();
if (columnName == string.Empty)
{
continue;
}
var column = new DataColumn(columnName, GetColumnDataType(lineLower));
if (columnName.Length < 2)
{
column.ColumnName = $"UnkCol{unkColumnCount++}";
}
currentTable.Columns.Add(column);
}
else if (definingTable)
{
if (lineLower.StartsWith(COLUMN_TYPE))
{
for (var i = 1; i < items.Length; i++)
{
var type = items[i].Trim();
if (type == string.Empty)
{
continue;
}
columnTypes.Add(type);
}
}
else if (lineLower.StartsWith(COLUMN_NAME))
{
for (int i = 1, j = 0; i < items.Length; i++)
{
var name = items[i].Trim();
if (name == string.Empty)
{
continue;
}
var columnType = columnTypes[j++];
var column = new DataColumn(name, GetColumnDataType(columnType));
if (name.Length < 2)
{
column.ColumnName = $"UnkCol{unkColumnCount++}";
}
currentTable.Columns.Add(column);
}
}
else if (lineLower.StartsWith(RECORD_IN))
{
var tableName = items[1].Trim();
if (tables.ContainsKey(tableName))
{
currentTable = tables[tableName];
var rowData = new object[currentTable.Columns.Count];
for (int i = 2, j = 0; i < items.Length; i++)
{
var item = items[i].Trim();
if (item == string.Empty)
{
continue;
}
rowData[j++] = CheckValue(replaceThis, item.TrimEnd(','));
}
currentTable.Rows.Add(rowData);
}
}
else if (lineLower.StartsWith(RECORD))
{
var rowData = new object[currentTable.Columns.Count];
for (int i = 1, j = 0; i < items.Length; i++)
{
if (j >= currentTable.Columns.Count)
{
break;
}
var item = items[i].Trim();
if (item == string.Empty)
{
continue;
}
rowData[j++] = CheckValue(replaceThis, item.TrimEnd(','));
}
currentTable.Rows.Add(rowData);
}
}
else
{
if (tables.ContainsKey(items[0].Trim()))
{
var existingTable = tables[items[0].Trim()];
var columnCount = existingTable.Columns.Count;
var readColumns = 0;
var rowData = new object[columnCount];
for (var i = 1; ; i++)
{
if (readColumns == columnCount)
{
break;
}
if (items.Length < i)
{
throw new Exception($"Could not read all columns of line {lineNumber}");
}
var columnText = items[i].Trim();
if (columnText == string.Empty)
{
continue;
}
columnText = columnText.TrimEnd(',').Trim('"');
rowData[readColumns++] = columnText;
}
existingTable.Rows.Add(rowData);
}
}
}
}
}
}
}