// Copyright (C) 2003 Daisuke Arai <darai@users.sourceforge.jp>
// Copyright (C) 2004-2007 panacoran <panacoran@users.sourceforge.jp>
// 
// This program is part of Protra.
//
// Protra is free software: you can redistribute it and/or modify it
// under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, see <http://www.gnu.org/licenses/>.
// 
// $Id: Price.cs 311 2010-03-21 12:44:47Z panacoran $

using System;
using System.Collections;
using System.Collections.Generic;

namespace Protra.Lib.Db
{
	/// <summary>
	/// Priceテーブルのレコードを表す。
	/// </summary>
	public class Price: IRecord
	{
		private int brandId;
		private DateTime date;
		private int open;
		private int high;
		private int low;
		private int close;
		private double volume;

		/// <summary>
		/// 既定のコンストラクタ
		/// </summary>
		public Price() {}

		/// <summary>
		/// コンストラクタ
		/// </summary>
		public Price(int brandId, DateTime date,
					 int open, int high, int low, int close, double volume)
		{
			this.brandId = brandId;
			this.date = date;
			this.open = open;
			this.high = high;
			this.low = low;
			this.close = close;
			this.volume = volume;
		}

		/// <summary>
		/// 銘柄IDを取得する。
		/// </summary>
		public int BrandId
		{
			get { return brandId; }
		}

		/// <summary>
		/// 銘柄IDに対応するBrandオブジェクトを取得する。
		/// </summary>
		public Brand Brand
		{
			get { return BrandTable.GetRecord(brandId); }
		}

		/// <summary>
		/// 日付を取得する。
		/// </summary>
		public DateTime Date
		{
			get { return date; }
		}

		/// <summary>
		/// 始値を取得する。
		/// </summary>
		public int Open
		{
			get { return open; }
		}

		/// <summary>
		/// 高値を取得する。
		/// </summary>
		public int High
		{
			get { return high; }
		}

		/// <summary>
		/// 安値を取得する。
		/// </summary>
		public int Low
		{
			get { return low; }
		}

		/// <summary>
		/// 終値を取得する。
		/// </summary>
		public int Close
		{
			get { return close; }
		}

		/// <summary>
		/// 出来高を取得する。
		/// </summary>
		public double Volume
		{
			get { return volume; }
		}

		/// <summary>
		/// 分割比率を適用する。
		/// </summary>
		public void Split(double ratio)
		{
			open = (int)(open / ratio);
			high = (int)(high / ratio);
			low = (int)(low / ratio);
			close = (int)(close / ratio);
			volume *= ratio;
		}

		/// <summary>
		/// データベースのレコードをオブジェクトに変換する。
		/// </summary>
		/// <param name="values">ネイティブ形式の値の配列</param>
		/// <returns>変換後のPriceオブジェクトを返す。</returns>
		public Object ToObject(Object[] values)
		{
			return new Price((int)values[0], // @Id
							 (DateTime)values[1], // @Date
							 (int)values[2], // @Open
							 (int)values[3], // @High
							 (int)values[4], // @Low
							 (int)values[5], // @Close
							 (double)values[6]); // @Volume
		}

		/// <summary>
		/// インスタンスを文字列表現に変換する。
		/// </summary>
		/// <returns>文字列表現を返す。</returns>
		public override string ToString()
		{
			return string.Format(
				"Price[Id={0}, {1}, Open={2}, Low={3}, High={4}, Close={5}, Volume={6}]",
				brandId, date.ToString("d"), open, low, high, close, volume);
		}
	}

	/// <summary>
	/// Priceテーブルを操作する。
	/// </summary>
	public class PriceTable
	{
		private static Connection conn;
		private static Connection Conn
		{
			get 
			{
				if (conn != null)
					return conn;
				conn = new Connection("protra");
				return conn;
			}
		}

		/// <summary>
		/// 銘柄IDに対応する銘柄の日足のデータを取得する。
		/// </summary>
		/// <param name="brand">銘柄レコードを指定する。</param>
		/// <returns>Priceの配列を返す。</returns>
		public static Price[] PriceList(Brand brand)
		{
			string sql = string.Format(
				"SELECT * FROM Price WHERE @BrandId={0} ORDER BY @Date",
				brand.Id);
			ArrayList list = Conn.Query(sql, new Price());
            try
            {
                foreach (var split in GlobalEnv.BrandData[brand.NewId].Split)
                {
                    if (split.Date > ((Price)list[list.Count - 1]).Date)
                        continue;
                    foreach (Price price in list)
                        if (price.Date < split.Date)
                            price.Split(split.Ratio);
                        else
                            break;
                }
            }
            catch (KeyNotFoundException)
            { }
			return (Price[])list.ToArray(typeof(Price));
		}

		/// <summary>
		/// 銘柄IDに対応する銘柄の週足のデータを取得する。
		/// </summary>
		/// <param name="brand">銘柄レコードを指定する。</param>
		/// <returns>Priceの配列を返す。</returns>
		public static Price[] WeeklyPriceList(Brand brand)
		{
			return WeeklyPriceList(brand, false);
		}

		/// <summary>
		/// 銘柄IDに対応する銘柄の週足のデータを取得する。
		/// </summary>
		/// <param name="brand">銘柄レコードを指定する。</param>
		/// <param name="needLastWeek">
		/// 最終週のデータがそろわなくても値が必要な場合はtrueを指定する。
		/// </param>
		public static Price[] WeeklyPriceList(Brand brand, bool needLastWeek)
		{
			Price[] dailyList = PriceTable.PriceList(brand);
			if (dailyList.Length == 0)
				return dailyList;

			ArrayList weeklyList = new ArrayList();
			DateTime date = dailyList[0].Date;
			int high = 0;
			int low = 0;
			int open = 0;
			int close = 0;
			double volume = 0;
			DayOfWeek previous_dow = DayOfWeek.Sunday;

			foreach (Price daily in dailyList)
			{
				if (previous_dow > daily.Date.DayOfWeek)
				{
					weeklyList.Add(new Price(brand.Id, date,
						open, high, low, close, volume));
					date = daily.Date;
					high = daily.High; low = daily.Low;
					open = daily.Open; close = daily.Close;
					volume = daily.Volume;
				}
				else
				{
					if (daily.High > high)
						high = daily.High;
					if (low == 0 || (daily.Low > 0 && daily.Low < low))
						low = daily.Low;
					if (open == 0)
						open = daily.Open;
					close = daily.Close;
					volume += daily.Volume;
				}
				previous_dow = daily.Date.DayOfWeek;
			}

			if (Utils.IsLastOpenDateOfWeek(dailyList[dailyList.Length - 1].Date) || needLastWeek)
				weeklyList.Add(new Price(brand.Id, date,
										 open, high, low, close, volume));
			return (Price[])weeklyList.ToArray(typeof(Price));
		}

        /// <summary>
        /// 指定した銘柄IDのレコードの日付のうち最も新しいものを返す。
        /// </summary>
        /// <param name="brandId">銘柄IDを指定する。</param>
        /// <returns>最も新しい日付。レコードが存在しない場合にはDateTime.MinValue。</returns>
        public static DateTime MaxDateById(int brandId)
        {
            string sql = "SELECT MAX(@Date) FROM Price WHERE @BrandId = " + brandId;
            object o = Conn.QueryScalar(sql);
            return o == DBNull.Value ? DateTime.MinValue : (DateTime)o;
        }

		/// <summary>
		/// すべてのレコードの日付のうち最も新しいものを返す。
		/// </summary>
		/// <returns>最も新しい日付。レコードが存在しない場合にはDateTime.MinValue。</returns>
		public static DateTime MaxDate()
		{
			string sql = "SELECT MAX(@Date) FROM Price";
			object o = Conn.QueryScalar(sql);
			return o == DBNull.Value ? DateTime.MinValue : (DateTime)o;
		}

		/// <summary>
		/// テーブルにデータを追加する。
		/// </summary>
		/// <param name="brandId">銘柄ID</param>
		/// <param name="date">日付</param>
		/// <param name="open">始値</param>
		/// <param name="high">高値</param>
		/// <param name="low">安値</param>
		/// <param name="close">終値</param>
		/// <param name="volume">出来高</param>
		/// <returns>追加したレコードのPriceオブジェクト。</returns>
		public static Price Add(int brandId, DateTime date,
			int open, int high, int low, int close, double volume)
		{
			string sql = string.Format(
				"INSERT INTO Price VALUES({0}, #{1}#, {2}, {3}, {4}, {5}, {6})",
				brandId, date.ToString("d"),
				open, high, low, close, volume);
			Conn.Execute(sql);
			return new Price(brandId, date, open, high, low, close, volume);
		}

		/// <summary>
		/// 指定された日付以降のデータを削除する。
		/// </summary>
		/// <param name="date">日付</param>
		public static void Delete(DateTime date)
		{
			string sql = string.Format(
				"DELETE FROM Price WHERE @Date>=#{0}#",
				date.ToString("d"));
			Conn.Execute(sql);
		}

		/// <summary>
		/// 全データを削除する。
		/// </summary>
		public static void Delete()
		{
			Conn.Execute("DELETE FROM Price");
		}

	}
}
