using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Daifugou
{
    using CardGame;

    public class Game
    {
        #region 初期化

        public Game()
        {
            this.state.Players = new List<Player>();
            this.state.CardsOnTable = new CardSet();
        }

        #endregion
        #region ユーティリティ関数

        #region ランクがらみ

        /// <summary>
        /// 大富豪ルールにおけるカードの序列を返す。
        /// （革命が起きていないとき）強いカードほど大きな値を返す。
        /// </summary>
        /// <param name="a">カード。</param>
        /// <returns>序列。</returns>
        /// <remarks>
        /// ジョーカーのときRankOfJokerを返す。
        /// </remarks>
        public static int Rank(Card a)
        {
            if (a.Suit == Suit.Joker)
                return Card.RankOfJoker;

            return Rank(a.Rank);
        }

        public static int Rank(int x)
        {
            if (x <= 2)
                return x + 11;
            return x - 2;
        }

        public static int Rank(Card a, bool revolution)
        {
            if (a.Suit == Suit.Joker)
                return Card.RankOfJoker;

            return Rank(a.Rank, revolution);
        }

        public static int Rank(int x, bool revolution)
        {
            if (revolution)
            {
                if (x <= 2)
                    return 3 - x;
                return 16 - x;
            }

            return Rank(x);
        }

        /// <summary>
        /// カードのランクを大富豪ルールで比較。
        /// </summary>
        /// <param name="a">オペランド左辺。</param>
        /// <param name="b">オペランド右辺。</param>
        /// <returns>IComparable.CompareTo 準拠。</returns>
        public static int Compare(Card a, Card b, bool revolution)
        {
            // ジョーカーは常に最強。
            if (a.Suit == Suit.Joker)
            {
                if (b.Suit == Suit.Joker)
                    return 0;
                else
                    return 1;
            }
            else if (b.Suit == Suit.Joker)
                return -1;

            // ジョーカー以外。
            int result = Rank(a).CompareTo(Rank(b));

            // 革命時は序列が逆。
            if (revolution) result = -result;

            return result;
        }

        public static int Compare(int a, int b, bool revolution)
        {
            // ジョーカーは常に最強。
            if (a == Card.RankOfJoker)
            {
                if (b == Card.RankOfJoker)
                    return 0;
                else
                    return 1;
            }
            else if (b == Card.RankOfJoker)
                return -1;

            // ジョーカー以外。
            int result = a.CompareTo(b);

            // 革命時は序列が逆。
            if (revolution) result = -result;

            return result;
        }

        public int Compare(int a, int b)
        {
            return Compare(a, b, this.state.Revolution);
        }

        /// <summary>
        /// a より b の方が1ランクだけ高ければ true。
        /// </summary>
        /// <param name="a">オペランド1</param>
        /// <param name="b">オペランド2</param>
        /// <param name="revolution">革命状態かどうか</param>
        /// <returns>a より b が1ランクだけ高ければ true</returns>
        public static bool IsAdjacent(int a, int b, bool revolution)
        {
            if (b == Card.RankOfJoker)
                return true;

            if (revolution)
                return a - 1 == b;
            else
                return a + 1 == b;
        }

        public bool IsAdjacent(int a, int b)
        {
            return IsAdjacent(a, b, this.state.Revolution);
        }

        #endregion
        #region カードが出せるかどうか判定

        public static bool CanPlay(System.Collections.IEnumerable cards, System.Collections.IEnumerable table, int rank, Suit suit, Mode mode, bool revolution)
        {
            return CanPlay(cards.Cast<Card>(), table.Cast<Card>(), rank, suit, mode, revolution);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="hand">自分の手札。</param>
        /// <param name="table">場に出ているカード。</param>
        /// <param name="rank">場に出ているカードの（最高）ランク。</param>
        /// <param name="suit">場に出ているカードのスイート。</param>
        /// <param name="mode">ゲームモード</param>
        /// <param name="revolution">trueなら革命状態。</param>
        /// <returns></returns>
        public static bool CanPlay(IEnumerable<Card> cards, IEnumerable<Card> table, int rank, Suit suit, Mode mode, bool revolution)
        {
            // そもそもまともにカードが送られてきてるかどうかチェック。
            if (cards == null)
                return false;

            var aCards = cards.ToArray();

            if (aCards.Length == 0)
                return false;

            // 初手なら何でも出し放題。
            if (mode == Mode.First)
            {
                if(aCards.Length == 1)
                    return true;

                if (IsSameRank(aCards) != 0)
                    return true;

                if (IsSequence(aCards) != 0)
                    return true;

                return false;
            }

            var aTable = table.ToArray();

            // 場札と手札の枚数比較。
            if (aCards.Length != aTable.Length)
                return false;

            // 複数枚。
            if (mode.Match(Mode.Multiple) && IsSameRank(aCards) == 0)
                return false;

            // 連番。
            int min;
            int max = IsSequence(aCards, out min);

            if (mode.Match(Mode.Sequence) && max == 0)
                return false;

            // 連番縛り。
            if (mode.Match(Mode.SequenceBound) && !IsAdjacent(rank, min, revolution))
                return false;

            // スイート縛り。
            if (mode.Match(Mode.SuitBound))
            {
                if (aCards[0].Suit != Suit.Joker && aCards[0].Suit != suit)
                    return false;
            }

            // ランク的にOKかどうか判定。
            if (Compare(rank, min, revolution) >= 0)
                return false;

            return true;
        }

        #endregion
        #region 連番とか同ランクとかの判定

        /// <summary>
        /// 全部同じランクのカードかどうかを判定。
        /// </summary>
        /// <param name="cards">判定対象。</param>
        /// <returns>ランクがそろってなかったら0、そろってたらランクを返す。</returns>
        public static int IsSameRank(IEnumerable<Card> cards)
        {
            var uniquified = cards
                .Where(x => x.Suit != Suit.Joker)
                .Select(x => x.Rank)
                .Distinct();

            if (uniquified.Count() <= 1)
            {
                foreach (var card in cards)
                {
                    if (card.Suit != Suit.Joker)
                        return Rank(card);
                }
                // 全部ジョーカー
                return Card.RankOfJoker;
            }

            // 条件を満たさなかった場合は0を返す。
            return 0;
        }

        /// <summary>
        /// 全部同じスイートのカードかどうかを判定。
        /// </summary>
        /// <param name="cards">判定対象。</param>
        /// <returns>全部同じならtrue。</returns>
        /// <remarks>
        /// ジョーカーのワイルドカード判定はしない。
        /// </remarks>
        public static bool IsSameSuit(IEnumerable<Card> cards)
        {
            var uniquified = cards.Select(x => x.Suit).Distinct();
            return uniquified.Count() == 1;
        }

        /// <summary>
        /// 同色連番かどうかを判定。
        /// </summary>
        /// <param name="cards">判定対象。</param>
        /// <param name="minRank">最小ランク。</param>
        /// <returns>条件を満たしてなかったら0、満たしていたら最大ランクを返す。</returns>
        public static int IsSequence(IEnumerable<Card> cards, out int minRank)
        {
            int jokers = cards.Count(x => x.Suit == Suit.Joker);

            if (!IsSameSuit(cards.Where(x => x.Suit != Suit.Joker)))
            {
                minRank = 0;
                return 0;
            }

            var ranks =
                from c in cards
                where c.Suit != Suit.Joker
                select Rank(c) into rank
                orderby rank
                select rank;

            int prev = -1;
            foreach (var rank in ranks)
            {
                if (prev == -1)
                {
                    prev = rank;
                    continue;
                }

                var dif = rank - prev;

                if (dif == 0)
                {
                    minRank = 0;
                    return 0;
                }

                if (dif == 1)
                {
                    prev = rank;
                    continue;
                }

                --dif;
                if (dif <= jokers)
                {
                    jokers -= dif;
                    prev = rank;
                    continue;
                }
                else
                {
                    minRank = 0;
                    return 0;
                }
            }

            minRank = ranks.Min();
            return ranks.Max();
        }

        public static int IsSequence(IEnumerable<Card> cards)
        {
            int dummy;
            return IsSequence(cards, out dummy);
        }

        #endregion
        #region OrderByで使う順序取得関数

        public static int SortByRank(Card c)
        {
            if (c.Suit == Suit.Joker)
                return 56;

            var rank = Rank(c);
            var suit = Card.ToInt(c.Suit);

            return rank * 4 + suit;
        }

        public static int SortBySuit(Card c)
        {
            if (c.Suit == Suit.Joker)
                return 56;

            var rank = Rank(c);
            var suit = Card.ToInt(c.Suit);

            return rank + suit * 14;
        }

        #endregion

        #endregion
        #region 状態の取得

        /// <summary>
        /// 参加プレイヤーID一覧。
        /// </summary>
        public IEnumerable<int> Players
        {
            get { return this.state.Players.Select(x => x.Id); }
        }

        /// <summary>
        /// 手札を取得。
        /// </summary>
        /// <param name="id">プレイヤーID。</param>
        /// <returns>手札。</returns>
        public IEnumerable<Card> GetHand(int id)
        {
            var me = this.state.Players.SingleOrDefault(x => x.Id == id);
            if (me == null)
                return null;
            return me.Hand;
        }

        /// <summary>
        /// ターン一周分の「見える情報」を取得する。
        /// </summary>
        /// <param name="id">プレイヤーID。</param>
        /// <remarks>
        /// 大富豪における「見える情報」は、他のプレイヤーが出したカードと、他のプレイヤーの残り枚数。
        /// </remarks>
        /// <returns>ターン一周分の「見える情報」</returns>
        public History GetHistory()
        {
            if (this.state.Players.Count == 0)
                return null;

            // 手番のプレイヤーからかどうかを確認。
            var current = this.state.Players[this.state.CurrentPlayerIndex];

            var id = current.Id;

            History h = new History();

            var i = this.state.CurrentPlayerIndex + 1;
            if (i >= this.state.Players.Count) i = 0;

            while (i != this.state.CurrentPlayerIndex)
            {
                var p = this.state.Players[i];

                h.Add(new VisibleInfo
                {
                    Count = p.Hand.Count,
                    Previous = p.Previous, //todo 安全を期すならCloneした方がいい
                });

                ++i;
                if (i >= this.state.Players.Count) i = 0;
            }

            return h;
        }

        /// <summary>
        /// 手番のプレイヤーのID。
        /// </summary>
        public int CurrentPlayer
        {
            get { return this.state.CurrentPlayer.Id; }
        }

        /// <summary>
        /// 上がり済みのプレーヤーのIDを勝った順に返す。
        /// </summary>
        public IEnumerable<int> Winners
        {
            get { return this.state.Winners.Select(x => this.state.Players[x].Id); }
        }

        /// <summary>
        /// 場に出ているカード。
        /// </summary>
        public IEnumerable<Card> CardsOnTable
        {
            get { return this.state.CardsOnTable; }
        }

        /// <summary>
        /// 現在のゲームモード。
        /// </summary>
        public Mode CurrentMode
        {
            get
            {
                return this.state.Mode;
            }
        }

        /// <summary>
        /// 現在場に出ているカードの最高ランク。
        /// </summary>
        public int CurrentRank
        {
            get
            {
                return this.state.Rank;
            }
        }

        /// <summary>
        /// （スイート縛り時のみ利用）
        /// 現在場に出ているカードのスイート。
        /// </summary>
        public Suit CurrentSuit
        {
            get
            {
                return this.state.Suit;
            }
        }

        /// <summary>
        /// 
        /// </summary>
        public bool Revolution
        {
            get { return this.state.Revolution; }
        }

        #endregion
        #region ゲーム開始までの流れ

        /// <summary>
        /// プレイヤーの追加。
        /// </summary>
        /// <param name="id">プレイヤーID</param>
        public void Join(int id)
        {
            this.state.Players.Add(new Player { Id = id });
        }

        /// <summary>
        /// プレイ開始。カードを配布。
        /// </summary>
        public void Deal()
        {
            foreach (var p in this.state.Players)
            {
                p.Hand.Clear();
            }

            this.state.Clear();

            Card.Deal(Card.ShuffledDeck(), this.state.Players.Select(x => x.Hand).ToArray());
        }

        // 富豪と貧民の間のカード交換

        #endregion
        #region ゲーム進行

        #region モード判定

        void MakeRevolution()
        {
            this.state.Revolution = !this.state.Revolution;
        }

        /// <summary>
        /// 初手のモード判定。
        /// </summary>
        /// <param name="cards">出そうとしているカードの組。</param>
        /// <returns>出せない組み合わせを出そうとしてたら false を返す。</returns>
        bool PlayFirst(IEnumerable<Card> cards)
        {
            var c = cards.ToArray();

            // シングル
            if (c.Length == 1)
            {
                this.state.Mode = Mode.Normal;
                this.state.Rank = Rank(c[0]);
                this.state.Suit = c[0].Suit;
                this.state.Count = 1;
                this.state.Turn = Turn.Second;

                return true;
            }

            // 同ランク複数
            var rank = IsSameRank(c);
            if (rank != 0)
            {
                this.state.Mode = Mode.Multiple;
                this.state.Rank = rank;
                this.state.Suit = Suit.None; // 今のところ、複数枚のスイート縛りは想定外。できないことはないんだけど。
                this.state.Count = c.Length;
                this.state.Turn = Turn.Second;

                if (c.Length >= 4)
                    this.MakeRevolution();

                return true;
            }

            // 同色連番。
            rank = IsSequence(c);
            if (rank != 0)
            {
                Suit suit = c.Select(x => x.Suit).Where(x => x != Suit.Joker).Distinct().Single();

                this.state.Mode = Mode.Sequence;
                this.state.Rank = rank;
                this.state.Suit = suit;
                this.state.Count = c.Length;
                this.state.Turn = Turn.Second;

                if (c.Length >= 4)
                    this.MakeRevolution();

                return true;
            }

            // 出せない組み合わせ。
            return false;
        }

        /// <summary>
        /// 2手目のモード判定。
        /// </summary>
        /// <param name="cards">出そうとしているカードの組。</param>
        /// <returns>出せない組み合わせを出そうとしてたら false を返す。</returns>
        bool PlaySecond(IEnumerable<Card> cards)
        {
            return Play(cards, true);
        }

        /// <summary>
        /// 初手と2手目の以外のモード判定。
        /// </summary>
        /// <param name="cards">出そうとしているカードの組。</param>
        /// <returns>出せない組み合わせを出そうとしてたら false を返す。</returns>
        bool PlayOther(IEnumerable<Card> cards)
        {
            return Play(cards, false);
        }

        /// <summary>
        /// 2手目以降のモード判定。
        /// 2手目だけ、階段とかスイート縛りの判定が付け加わる。
        /// </summary>
        /// <param name="cards">出そうとしているカードの組。</param>
        /// <param name="second">2手目なら true。</param>
        /// <returns>出せない組み合わせを出そうとしてたら false を返す。</returns>
        bool Play(IEnumerable<Card> cards, bool second)
        {
            var c = cards.ToArray();

            if (this.state.Mode.Match(Mode.Normal))
            {
                return PlayNormal(c);
            }

            if (this.state.Mode.Match(Mode.Multiple))
            {
                return PlayMultiple(c);
            }

            if (this.state.Mode.Match(Mode.Sequence))
            {
                return PlaySequence(c);
            }

            return false;
        }

        private bool PlaySequence(Card[] c)
        {
            if (c.Length != this.state.Count)
                return false;

            // ランク比較
            int min;
            var rank = IsSequence(c, out min);
            if (rank == 0)
                return false;

            if (this.Compare(this.state.Rank, min) >= 0)
                return false;

            // 階段縛り
            if (this.state.Mode.Match(Mode.SequenceBound)
                && !this.IsAdjacent(this.state.Rank, min))
                return false;

            var suit = c.FirstOrDefault(x => x.Suit != Suit.Joker).Suit;
            //todo ↑Joker 2枚のときに困る

            // スイート縛り
            if (this.state.Mode.Match(Mode.SuitBound)
                && suit != this.state.Suit)
                return false;

            // この時点で出せること確定。
            // 2手目、縛りの判定
            if (this.state.Turn == Turn.Second)
            {
                if (this.state.Suit == suit)
                    this.state.Mode |= Mode.SuitBound;

                if (this.IsAdjacent(this.state.Rank, min))
                    this.state.Mode |= Mode.SequenceBound;

                this.state.Turn = Turn.Other;
            }

            // ランク更新
            if (rank == Card.RankOfJoker)
                this.state.Rank += 1;
            else
                this.state.Rank = rank;

            if (c.Length >= 4)
                this.MakeRevolution();

            return true;
        }

        private bool PlayMultiple(Card[] c)
        {
            if (c.Length != this.state.Count)
                return false;

            // ランク比較
            var rank = IsSameRank(c);
            if (rank == 0)
                return false;

            if (this.Compare(this.state.Rank, rank) >= 0)
                return false;

            // この時点で出せること確定。

            // 2手目、縛りの判定
            if (this.state.Turn == Turn.Second)
            {
                this.state.Turn = Turn.Other;
            }

            // ランク更新
            //if (rank == Card.RankOfJoker)
            //    this.state.Rank += 1;
            //else

            this.state.Rank = rank;

            if (c.Length >= 4)
                this.MakeRevolution();

            return true;
        }

        private bool PlayNormal(Card[] c)
        {
            if (c.Length != 1)
                return false;

            // ランク比較
            var rank = Rank(c[0]);
            var suit = c[0].Suit;

            if (this.Compare(this.state.Rank, rank) >= 0)
                return false;

            // スイート縛り
            if (this.state.Mode.Match(Mode.SuitBound)
                && suit != this.state.Suit
                && suit != Suit.Joker)
                return false;

            // この時点で出せること確定。

            // 2手目、縛りの判定
            if (this.state.Turn == Turn.Second)
            {
                if (this.state.Suit == suit)
                    this.state.Mode |= Mode.SuitBound;

                this.state.Turn = Turn.Other;
            }

            // ランク更新
            //if (rank == Card.RankOfJoker)
            //    this.state.Rank += 1;
            //else

            this.state.Rank = rank;

            return true;
        }

        #endregion

        /// <summary>
        /// カードを場に出す。
        /// 出せないカードを出そうとしたときにはfalseを返す。
        /// </summary>
        /// <param name="id">プレイヤーID</param>
        /// <param name="cards">出したいカード</param>
        /// <returns>成功したらtrue。</returns>
        public bool Play(int id, IEnumerable<Card> cards)
        {
            if (cards == null)
                return false;

            // 遅延評価で後からカードの種類が変わるような IEnumerable を渡されても平気なように。
            var aCards = cards.ToArray();

            if (aCards.Length == 0)
                return false;

            // 手番のプレイヤーからかどうかを確認。
            var current = this.state.Players[this.state.CurrentPlayerIndex];

            // ちゃんと持ってる手札かどうか確認。
            foreach (var card in aCards)
            {
                if (!current.Hand.Contains(card))
                    return false;
            }

            if (id != current.Id)
                return false;

            // 出せるかどうか判定＆状態更新。
            if (this.state.Turn == Turn.First)
            {
                if (!PlayFirst(aCards))
                    return false;
            }
            else if (this.state.Turn == Turn.Second)
            {
                if (!PlaySecond(aCards))
                    return false;
            }
            else
            {
                if (!PlayOther(aCards))
                    return false;
            }

            // current.Hand から出したカード消す
            foreach (var card in aCards)
            {
                current.Hand.Remove(card);
            }

            // 場札更新
            this.state.CardsOnTable = new CardSet(aCards); // いちいち new するのは非効率か。CardSet に Set メソッド作る？

            // 勝利判定
            if (current.Hand.Count == 0)
            {
                this.state.Winners.Add(this.state.CurrentPlayerIndex);
            }

            // 手番を進める
            this.state.LastPlayer = this.state.CurrentPlayerIndex;

            current.Previous.Clear();

            foreach (var c in aCards)
            {
                current.Previous.Add(c);
            }

            this.Progress();

            return true;
        }

        /// <summary>
        /// パスする。
        /// パスできないとき（自分の手番以外でこのメソッドを呼んだ場合）はfalseを返す。
        /// </summary>
        /// <param name="id">プレイヤーID</param>
        /// <returns>成功したらtrue。</returns>
        public bool Pass(int id)
        {
            if (this.state.Players.Count == 0)
                return false;

            // 手番のプレイヤーからかどうかを確認。
            var current = this.state.Players[this.state.CurrentPlayerIndex];

            if (id != current.Id)
                return false;

            current.Previous.Clear();

            // 手番を進める。
            this.Progress();

            return true;
        }

        /// <summary>
        /// 手番を進める。
        /// </summary>
        void Progress()
        {
            if(this.state.Winners.Count == this.state.Players.Count)
                return;

            do
            {
                this.ProgressOne();

                if (this.state.LastPlayer == this.state.CurrentPlayerIndex)
                {
                    // 場を流す
                    this.state.CardsOnTable.Clear();
                    this.state.Turn = Turn.First;
                    this.state.Mode = Mode.First;
                }
            }
            while (this.state.Winners.Contains(this.state.CurrentPlayerIndex));
        }

        /// <summary>
        /// 手番を進める。
        /// </summary>
        void ProgressOne()
        {
            ++this.state.CurrentPlayerIndex;

            if (this.state.CurrentPlayerIndex >= this.state.Players.Count)
                this.state.CurrentPlayerIndex = 0;

            this.state.Players[this.state.CurrentPlayerIndex].Previous.Clear();
        }

        #endregion
        #region フィールド

        GameState state;

        #endregion
    }
}
