// dmain.C Version 7.43  Copyleft (c) 杉本典夫
// Time-stamp: <2025-06-11 09:41:01 tac>
//
// dans共通関数を使ったdansプログラムのプロトタイプ。
//
// 使用ライブラリ:libdans.so, libfun.so
// コンパイル法 :g++ -O -s -W dmain dmain.C
//               -I/usr/local/include -L/usr/local/lib -ldans -lfun

#include <dans.H>               // dans用ヘッダファイル(mat.H含む)

using namespace dans;           // dans名前空間使用(名前空間matvec含む)

const vecstr USAGE =            // プログラム使用法
{
  "dmain  Version 7.43          Copyleft (Vo\\oV) 2025 杉本典夫",
  "書式:dmain  [<オプション> …]  [<データファイル> …]",
  "機能:プログラムのプロトタイプです。",
  "<オプション>",
  "  -c=[<開始ケース番号>][-[<終了ケース番号>]]:対象ケース番号を指定します",
  "  -dnsname[=<出力ファイル名>]:DANSデータを出力するファイル名を指定します。",
  "    オプション省略時:DANSデータ出力せず  -dnsname:-dnsname=dmain_dns.d",
  "  -f=<ファイル名>:指定されたファイルからオプションを読み込みます。",
  "  -h[=<コマンド名>]:オンラインマニュアルを表示します。(-h:-h=man)",
  "  -ngp={data|mse}[,…]:Ngraph用データを指定します。",
  "    data:データプロット  mse:平均値±標準誤差  オプション省略時:全て指定",
  "  -ngpname[=<出力ファイル名>]:Ngraph用データを出力するファイル名を指定します。",
  "    オプション省略時:Ngraph用データ出力せず  -ngpname:-ngpname=dmain_ngp.dat",
  "  -res=[-][{def|all|list|nd|stat}][,…]:解析結果の出力項目を指定します。",
  "    -:-を付けた項目を出力しない(-res=-:全ての項目を出力しない)",
  "    def:オプション省略時と同じ項目を出力(wc)  all:全ての項目を出力",
  "    list:データリスト  nd:データ数  stat:実数データの基礎統計量",
  "    オプション省略時:データに合わせて自動判定",
  "  -r<層別項目番号>=[<実数下限>][-[<実数上限>]]:実数層別項目指定",
  "  -s<層別項目番号>=<文字列>[,<文字列>,…]:文字層別項目指定",
  "    指定した実数範囲内または文字列と一致するケースだけ出力します。",
  "    -rと-sは混合して複数指定可能で、それらは「または(OR)」で結ばれます。"
};

const string Title = "=== DANSプログラムのプロトタイプ ==="; // 表題
const string DNSNAMEdef = "dmain_dns.d"; // 省略時DANSファイル名
const string NGPNAMEdef = "dmain_ngp.dat"; // 省略時Ngraphファイル名

class option                    // オプションクラス
{
private:
  string v_path;                // プログラムのパス
  string v_pname;               // プログラム名
  pairint cpair;                // 対象ケース番号
  lstra v_stralst;              // 層別情報リスト
  string v_dnsname;             // DANSファイル名
  string v_ngpname;             // Ngraphファイル名
  lstr ngplst;                  // Ngraph出力リスト
  mapsb ngpmap;                 // Ngraph出力の有無マップ
  lstr reslst;                  // 解析結果出力リスト
  mapsb resmap;                 // 解析結果出力の有無マップ
public:
  option(const char* c_pname = nullptr); // コンストラクタ
  ~option()                              // デストラクタ
  {}
  const string& path() const    // プログラムのパスを返す
  { return v_path; }
  const string& pname() const   // プログラム名を返す
  { return v_pname; }
  int cs() const                // 開始ケース番号を返す
  { return cpair.first; }
  int ce() const                // 終了ケース番号を返す
  { return cpair.second; }
  lstra& stralst()              // 層別情報リストを返す
  { return v_stralst; }
  const string& dnsname() const // DANSデータファイル名を返す
  { return v_dnsname; }
  const string& ngpname() const // Ngraphデータファイル名を返す
  { return v_ngpname; }
  bool ngp(const string& code); // Ngraph出力フラッグを返す
  bool res(const string& code); // 解析結果出力フラッグを返す
  void setdef()                 // 省略時オプション指定を設定する
  { getoptdef(*this); }
  void set(const string& optstr, char devc = OPTdev);
  // オプション指定によってオプションを設定する
  int check(size_t ni, size_t nc);  // オプションのチェックとセット
  int set_ngp();                // -ngpオプションのセット
  int set_res();                // -resオプションのセット
  bool smatch(datac& cdata) const // 層別のチェック
  { return stramatch(v_stralst, cdata); }
};

option::option(const char* c_pname) : v_path(), v_pname(), cpair(1, INT_MAX), v_stralst(), v_dnsname(), v_ngpname(), ngplst(), ngpmap(), reslst(), resmap()
// コンストラクタ
{
  if (c_pname == nullptr) return; // 初期設定せず
  v_pname = getname(c_pname);      // パス名なしのプログラム名取得
  v_path = c_pname;
  strsize_t idx = v_path.find(v_pname);
  if (idx != string::npos) v_path.erase(idx); // プログラム名削除
}

bool option::ngp(const string& code) // Ngraph出力フラッグを返す
{
  mapsb_citr ngppos = ngpmap.find(code);
  if (ngppos == ngpmap.end()) return false; // code無し！
  return ngpmap[code];                     // true/falseを返す
}

bool option::res(const string& code) // 結果出力フラッグを返す
{
  mapsb_citr respos = resmap.find(code);
  if (respos == resmap.end()) return false; // code無し！
  return resmap[code];                     // true/falseを返す
}

void option::set(const string& optstr, char devc)
// オプション指定によってオプションを設定する
{
  string opt[2];                // オプションのopt[0]:項目,opt[1]:内容
  if (getopt(opt, optstr, devc) < 1) return; // オプション指定なし
  int err = 0;
  if (opt[0] == "c")            // 対象ケース番号の指定
  {
    if (getrange(cpair.first, cpair.second, opt[1]) < 1)
    { err = badopt(pname(), optstr, USAGE, 1.0, (double)INT_MAX); }
    else if (cpair.first < 1 || cpair.first > cpair.second)
    { err = badopt(pname(), optstr, USAGE, 1.0, (double)INT_MAX); }
  }
  else if (opt[0] == "dnsname") // DANSファイル名指定
  {
    if (opt[1].empty()) v_dnsname = DNSNAMEdef;
    else v_dnsname = opt[1];
  }
  else if (opt[0] == "f")       // オプション指定ファイル
  { err = fgetopt(*this, opt[1], true); }
  else if (opt[0] == "h")       // オンラインヘルプ表示
  { err = help(pname(), USAGE, opt[1]); }
  else if (opt[0] == "ngp")     // Ngraph出力項目指定
  {
    if (strsplit(ngplst, opt[1], CHRdelm) < 1) err = badopt(pname(), optstr, USAGE);
  }
  else if (opt[0] == "ngpname")     // Ngraphファイル名指定
  {
    if (opt[1].empty()) v_ngpname = NGPNAMEdef;
    else v_ngpname = opt[1];
  }
  else if (opt[0] == "res")     // 解析結果の出力指定
  {
    if (strsplit(reslst, opt[1], CHRdelm) < 1) err = badopt(pname(), optstr, USAGE);
  }
  else if (opt[0][0] == 'r' || opt[0][0] == 's') // 層別指定
  {
    stra tstra;
    char* prt = nullptr;
    int ino = strtol(opt[0].substr(1).c_str(), &prt, 0);
    if (tstra.get(opt[0][0], ino, opt[1]) == datanon) err = badopt(pname(), optstr, USAGE);
    v_stralst.push_back(tstra);
  }
  else                          // 間違ったオプション指定
  { err = nonopt(pname(), optstr, USAGE); }
  if (err > 0) throw help_except("ヘルプ表示");
  if (err < 0) throw option_error(string("オプション指定違反→") + optstr);
}

int option::check(size_t ni, size_t nc) // オプションのチェックとセット
{
  if (cpair.second == INT_MAX) cpair.second = (int)nc;
  else if (cpair.second > (int)nc) return disperr(pname(), "-cオプションが不適切です！");
  if (straok(v_stralst, ni) == false) return disperr(pname(), "-rまたは-sオプションが不適切です！");
  if (set_ngp() < 0) return -2; // -ngpオプションのエラー
  if (set_res() < 0) return -3; // -resオプションのエラー
  return 0;                     // エラーなし
}

int option::set_ngp()           // -ngpオプションのセット
{
  ngpmap["data"] = ngpmap["mse"] = true;
  if (ngplst.size() > 0)
  {
    if (v_ngpname == "") return disperr(pname(), "-ngpnameオプションの指定が必要！");
    ngpmap["data"] = ngpmap["mse"] = false;
    for (const string& ngpstr : ngplst)
    {
      mapsb_citr ngppos = ngpmap.find(ngpstr);
      if (ngppos == ngpmap.end())
      { return disperr(pname(), "-ngp=" + ngpstr + "オプション指定が不適切です！"); }
      else
      { ngpmap[ngpstr] = true; }
    }
  }
  return 0;                     // エラーなし
}

int option::set_res()           // -resオプションのセット
{
  resmap["res"] = resmap["def"] = resmap["nd"] = true;
  resmap["list"] = resmap["stat"] = false;
  if (reslst.size() > 0)
  {
    resmap["def"] = resmap["nd"] = false;
    for (const string& resstr : reslst)
    {
      mapsb_citr respos = resmap.find(resstr);
      if (resstr == "-")        // 全て出力せず
      { resmap["res"] = false; }
      else if (resstr[0] == '-' && resstr.size() > 1) // 特定の項目を出力せず
      {
        respos = resmap.find(resstr.substr(1));
        if (respos == resmap.end())
        { return disperr(pname(), "-res=" + resstr + "オプション指定が不適切です！"); }
        else
        { resmap[resstr.substr(1)] = false; }
      }
      else if (resstr == "def") // 省略時出力
      { resmap["def"] = resmap["nd"] = true; }
      else if (resstr == "all") // 全て出力
      {
        resmap["def"] = resmap["nd"] = resmap["stat"] = resmap["list"] = true;
      }
      else if (respos == resmap.end())
      { return disperr(pname(), "-res=" + resstr + "オプション指定が不適切です！"); }
      else
      { resmap[resstr] = true; }
    }
  }
  return 0;                     // エラーなし
}

class varclass                  // 変数クラス
{
private:
  int v_nc;                      // 解析対象ケース数
  vector<statv<double>> statvec; // 統計量ベクトル
  restype v_resvar;              // 計算結果の種類
public:
  varclass(size_t ni) : v_nc(0), statvec(ni + 1), v_resvar(resnon) // コンストラクタ
  {}
  ~varclass()                   // デストラクタ
  {}
  int nc() const                // 解析対象ケース数を返す
  { return v_nc; }
  int n(size_t idx) const       // 例数を返す
  { return (idx < statvec.size()) ? (statvec[idx]).n() : 0; }
  double mean(size_t idx) const // 平均値を返す
  { return (idx < statvec.size()) ? (statvec[idx]).mean() : BLANKf; }
  double vr(size_t idx) const   // 不偏分散を返す
  { return (idx < statvec.size()) ? (statvec[idx]).vr() : BLANKf; }
  double se(size_t idx) const   // 標準誤差を返す
  { return (idx < statvec.size()) ? (statvec[idx]).se() : BLANKf; }
  restype resvar() const        // 計算結果の種類を返す
  { return v_resvar; }
  void cal(option& opt, ldata& datalst); // データを集計して変数をセットする
};

void varclass::cal(option& opt, ldata& datalst)
// データを集計して変数をセットする
{
  size_t ni = statvec.size() - 1;
  v_nc = 0;
  int cno = 1;
  for (auto& cdata : datalst)
  {
    if (opt.cs() <= cno && cno <= opt.ce() && opt.smatch(cdata))
    {
      for (size_t j = 0; j < ni; ++j)
      {
        if (cdata.fdata(j) != BLANKf) statvec[j].sigma(cdata.fdata(j));
      }
      ++v_nc;
    }
    ++cno;
  }
  for (size_t j = 0; j < ni; ++j) statvec[ni] += statvec[j];
  v_resvar = (v_nc > 0) ? resok : reserr;
}

class dmainclass                // dansメインクラス
{
private:
  string fname;                 // ファイル名
  idd id;                       // データ識別情報
  ldata datalst;                // データリスト
  void put_resid(ostream& fout);
  // 解析結果のタイトルとデータ識別情報を出力する
  void put_res(ostream& fout, option& opt, const varclass& var);
  // 解析結果を出力する
  void put_dns(option& opt, const varclass& var);
  // DANSデータを出力する
  void plot_ngp(option& opt, const varclass& var);
  // Ngraph用データを出力する
  void plot_ngpid(ofstream& fout);
  // Ngraph用データのタイトルとデータ識別情報を出力する
  void plot_data(ofstream& fout); // Ngraph用に生データを出力する
  void plot_mse(ofstream& fout, const varclass& var) const;
  // Ngraph用に平均値±SEを出力する
public:
  dmainclass() : fname(), id(), datalst() // コンストラクタ
  {}
  ~dmainclass()                 // デストラクタ
  {}
  int get(const char* c_fname, option& opt); // データを読み込む
  void put(option& opt);        // 集計結果を出力する
};

void dmainclass::put_resid(ostream& fout)
// 解析結果のタイトルとデータ識別情報を出力する
{
  puttitle(fout, Title);
  fout << "\n";
  id.put(fout);
}

void dmainclass::put_res(ostream& fout, option& opt, const varclass& var)
// 解析結果を出力する
{
  constexpr int LENline = 76; // 区切り線の長さ
  put_resid(fout);
  fout << "\n";
  bool putres = false;
  if (opt.res("list"))          // データリスト出力
  {
    int cno = 1;
    for (auto& cdata : datalst)
    {
      if (opt.cs() <= cno && cno <= opt.ce() && opt.smatch(cdata))
      { fout << "ケース" << cno << ":";  cdata.put(fout); }
      ++cno;
    }
    putres = true;
  }
  if (opt.res("nd"))            // データ数出力
  {
    if (putres) fout << "\n";
    else putres = true;
    fout << "・層別数=" << id.ns() << " 項目数=" << id.ni() << " ケース数=" << id.nc() << "\n";
    fout << "  cs=" << opt.cs() << " ce=" << opt.ce() << " 解析対象ケース数=" << var.nc() << "\n";
  }
  if (var.resvar() == reserr)
  {
    fout << "  ※データなし！\n";
    return;
  }
  if (opt.res("stat"))          // 統計量出力
  {
    if (putres) fout << "\n";
    fout << "・各項目の基礎統計量\n";
    putline(fout, LENline);
    for (size_t j = 0; j < id.ni(); ++j)
    {
      fout << "項目" << setw(4) << j + 1 << ":";
      putmsd(fout, var.n(j), var.mean(j), var.vr(j));
    }
    putline(fout, LENline);
    fout << "全体    :";
    putmsd(fout, var.n(id.ni()), var.mean(id.ni()), var.vr(id.ni()));
  }
}

void dmainclass::put_dns(option& opt, const varclass& var)
// DANSデータを出力する
{
  ofstream fout;
  if (ofopen(fout, (opt.dnsname()).c_str()) == false) return; // ファイルオープン失敗
  id.put(fout, formtag);
  for (auto& cdatai : datalst) cdatai.put(fout);
}

void dmainclass::plot_ngp(option& opt, const varclass& var)
// Ngraph用データ出力
{
  ofstream fout;
  if (ofopen(fout, (opt.ngpname()).c_str()) == false) return; // ファイルオープン失敗
  bool delml = false;
  if (opt.ngp("data"))          // 生データ
  {
    fout << "-s1 -zx0," << id.ni() + 1 << ",1 -dline -w40\n";
    plot_ngpid(fout);
    plot_data(fout);
    delml =true;
  }
  if (opt.ngp("mse"))           // 平均値±SE
  {
    if (delml) fout << NGPdelm << "\n";
    else fout << "-s1 -zx0," << id.ni() + 1 << ",1 -dline -w80\n";
    plot_ngpid(fout);
    plot_mse(fout, var);
  }
}

void dmainclass::plot_ngpid(ofstream& fout)
// Ngraph用データのタイトルとデータ識別情報を出力する
{
  fout << "%";  puttitle(fout, Title);
  fout << "%\n";
  id.put(fout, formngp);
}

void dmainclass::plot_data(ofstream& fout)
// Ngraph用に生データを出力する
{
  fout << "%生データ\n";
  bool delml2 = false;
  for (auto& cdatai : datalst)
  {
    if (delml2) fout << NGPdelm2 << "\n";
    else delml2 = true;
    for (size_t j = 0; j < id.ni(); ++j)
    {
      double fdata = cdatai.fdata(j);
      if (fdata != BLANKf) fout << j + 1 << " " << fdata << "\n";
    }
  }
}

void dmainclass::plot_mse(ofstream& fout, const varclass& var) const
// Ngraph用に平均値±SEを出力する
{
  fout << "%実測値平均値±SE\n";
  for (size_t j = 0; j < id.ni(); ++j)
  {
    fout << j + 1 << " " << var.mean(j) << " " << var.se(j) << " " << -var.se(j) << "\n";
  }
}

int dmainclass::get(const char* c_fname, option& opt) // データを読み込む
{
  if (c_fname == nullptr) fname.erase();
  else fname = c_fname;
  int nc = fgetdata(fname, id, datalst);
  if (nc < 0) throw runtime_error("実行時エラー！");
  if (opt.check(id.ni(), id.nc()) != 0) throw option_error("実行時オプションエラー！");
  if (id.append_stra(opt.stralst()) < 0) throw option_error("層別指定が間違っています！");
  return nc;                    // 読み込んだケース数を返す
}

void dmainclass::put(option& opt) // 結果を出力する
{
  varclass var(id.ni());        // 変数オブジェクト
  var.cal(opt, datalst);        // データを集計して変数をセットする
  if (opt.res("res"))
  {
    put_res(cout, opt, var);
    cout << "\n" << endl;
  }
  if (var.resvar() == reserr) return;
  if (opt.dnsname() != "") put_dns(opt, var); // DANSデータ出力
  if (opt.ngpname() != "") plot_ngp(opt, var); // Ngraph用データ出力
}

int main(int argc, char* argv[]) // メインルーチン
{
  dmainclass dmain;             // メインクラス変数
  option opt(argv[0]);          // オプション
  try                           // 例外監視
  {
    opt.setdef();               // オプションの初期設定
    bool fok = true;            // ファイル処理モードフラッグ
    for (int i = 1; i <= argc; ++i)
    {
      if (i >= argc)
      {
        if (fok) dmain.get(nullptr, opt); // 標準入力から読み込む
      }
      else if (*(argv[i]) == CHRopt)
      { opt.set(argv[i] + 1);  fok = true; }
      else
      { dmain.get(argv[i], opt);  fok = false; }
    }
    dmain.put(opt);
  }
  catch (help_except& helpex)   // ヘルプファイル表示
  { return 0; }
  catch (option_error& opterr)  // オプション指定エラー
  { return 1; }
  catch (runtime_error& rterr)  // 実行時エラー
  {
    disperr(opt.pname(), rterr.what());
    return 2;
  }
  catch (...)                   // 予期せぬ例外
  {
    disperr(opt.pname(), "内容不明なエラー発生！");
    return 255;
  }
  return 0;
}
