using System;
using System.Collections.Generic;
using System.Drawing;
using System.IO;
using System.Text;
using Ruby;
using VALUE = System.Int32;
using StarEngine.Core;

namespace StarEngine.Ruby
{
    public partial class RubyInterpreter
    {
        private ITexture GetTexture(VALUE rbTexture)
        {
            return this.GetCachedObject<ITexture>(rbTexture);
        }

        private void InitializeTextureClass()
        {
            VALUE cTexture = SERubyConsts.ForName("Texture");

            RUBY.rb_define_method(cTexture, "__cs_initialize__", this.cTexture_cs_initialize);
            RUBY.rb_define_private_method(cTexture, "initialize_copy", this.cTexture_initialize_copy);
            RUBY.rb_define_singleton_method(cTexture, "new", this.cTexture_new);
            RUBY.rb_define_singleton_method(cTexture, "load", this.cTexture_load);
            RUBY.rb_define_method(cTexture, "dispose", this.cTexture_dispose);
            RUBY.rb_define_method(cTexture, "disposed?", this.cTexture_disposed);
            RUBY.rb_define_method(cTexture, "get_pixel", this.cTexture_get_pixel);
            RUBY.rb_define_method(cTexture, "set_pixel", this.cTexture_set_pixel);
            RUBY.rb_define_method(cTexture, "change_hue", this.cTexture_change_hue);
            RUBY.rb_define_method(cTexture, "clear", this.cTexture_clear);
            RUBY.rb_define_method(cTexture, "fill", this.cTexture_fill);
            RUBY.rb_define_method(cTexture, "fill_rect", this.cTexture_fill_rect);
            RUBY.rb_define_method(cTexture, "render_text", this.cTexture_render_text);
            RUBY.rb_define_private_method(cTexture, "__cs_render_texture__", this.cTexture_cs_render_texture);
        }

        private VALUE cTexture_cs_initialize(VALUE self)
        {
            RUBY.Send(self, "__cs_set_finalizer__");
            return RUBY.Qnil;
        }

        private VALUE cTexture_initialize_copy(VALUE self, VALUE rbTexture)
        {
            ITexture csClonedTexture = (ITexture)this.GetTexture(rbTexture).Clone();
            this.AddCachedObject(self, csClonedTexture);
            return self;
        }

        private VALUE cTexture_new(VALUE self, VALUE rbWidth, VALUE rbHeight)
        {
            Size csSize = new Size(RUBY.NUM2INT(rbWidth), RUBY.NUM2INT(rbHeight));
            ITexture csTexture = this.GameEnvironment.TextureFactory.Create(csSize);
            VALUE rbTexture = RUBY.Send(self, "_orig_new");
            this.AddCachedObject(rbTexture, csTexture);
            RUBY.rb_iv_set(rbTexture, "@width", RUBY.INT2NUM(csTexture.Width));
            RUBY.rb_iv_set(rbTexture, "@height", RUBY.INT2NUM(csTexture.Height));
            return rbTexture;
        }

        private VALUE cTexture_load(VALUE self, VALUE path)
        {
            string csPath = PathExtensions.GetFullPathAndExtension(RUBY.StringValuePtr(path));
            ITexture csTexture;
            try
            {
                csTexture = this.GameEnvironment.TextureFactory.Load(csPath);
            }
            catch (FileNotFoundException e)
            {
                RUBY.rb_raise(SERubyConsts.ForName("::Errno::ENOENT"), e.FileName);
                return RUBY.Qnil;
            }
            catch (ArgumentException)
            {
                RUBY.rb_raise(SERubyConsts.ForName("StarEngineError"), "invalid image file");
                return RUBY.Qnil;
            }
            VALUE rbTexture = RUBY.Send(self, "_orig_new");
            this.AddCachedObject(rbTexture, csTexture);
            RUBY.rb_iv_set(rbTexture, "@width", RUBY.INT2NUM(csTexture.Width));
            RUBY.rb_iv_set(rbTexture, "@height", RUBY.INT2NUM(csTexture.Height));
            return rbTexture;
        }

        private VALUE cTexture_dispose(VALUE self)
        {
            RUBY.rb_check_frozen(self);
            ITexture csTexture = this.GetTexture(self);
            if (csTexture == null)
            {
                RUBY.rb_raise(SERubyConsts.ForName("StarEngineError"), "already disposed");
                return RUBY.Qnil;
            }
            csTexture.Dispose();
            this.RemoveCachedObject(RUBY.GetID(self));
            return RUBY.Qnil;
        }

        private VALUE cTexture_disposed(VALUE self)
        {
            return !this.ContainsCachedObject(self) ? RUBY.Qtrue : RUBY.Qfalse;
        }

        private VALUE cTexture_get_pixel(VALUE self, VALUE rbX, VALUE rbY)
        {
            ITexture csTexture = this.GetTexture(self);
            if (csTexture == null)
            {
                RUBY.rb_raise(SERubyConsts.ForName("StarEngineError"), "disposed texture");
                return RUBY.Qnil;
            }
            int csX = RUBY.NUM2INT(rbX);
            int csY = RUBY.NUM2INT(rbY);
            try
            {
                return ColorClass.ToRubyColor(csTexture.GetPixel(csX, csY));
            }
            catch (IndexOutOfRangeException)
            {
                RUBY.rb_raise(SERubyConsts.ForName("::IndexError"),
                    string.Format("index out of range: ({0},{1})", csX, csY));
                return RUBY.Qnil;
            }
        }

        private VALUE cTexture_set_pixel(VALUE self, VALUE rbX, VALUE rbY, VALUE rbColor)
        {
            RUBY.rb_check_frozen(self);
            ITexture csTexture = this.GetTexture(self);
            if (csTexture == null)
            {
                RUBY.rb_raise(SERubyConsts.ForName("StarEngineError"), "disposed texture");
                return RUBY.Qnil;
            }
            int csX = RUBY.NUM2INT(rbX);
            int csY = RUBY.NUM2INT(rbY);
            Color csColor = ColorClass.ToCSharpColor(rbColor);
            try
            {
                csTexture.SetPixel(csX, csY, csColor);
            }
            catch (IndexOutOfRangeException)
            {
                RUBY.rb_raise(SERubyConsts.ForName("::IndexError"),
                    string.Format("index out of range: ({0},{1})", csX, csY));
            }
            return RUBY.Qnil;
        }

        private VALUE cTexture_change_hue(VALUE self, VALUE rbDHue)
        {
            RUBY.rb_check_frozen(self);
            ITexture csTexture = this.GetTexture(self);
            if (csTexture == null)
            {
                RUBY.rb_raise(SERubyConsts.ForName("StarEngineError"), "disposed texture");
                return RUBY.Qnil;
            }
            csTexture.ChangeHue(RUBY.NUM2INT(rbDHue));
            return RUBY.Qnil;
        }

        private VALUE cTexture_clear(VALUE self)
        {
            RUBY.rb_check_frozen(self);
            ITexture csTexture = this.GetTexture(self);
            if (csTexture == null)
            {
                RUBY.rb_raise(SERubyConsts.ForName("StarEngineError"), "disposed texture");
                return RUBY.Qnil;
            }
            csTexture.Clear();
            return RUBY.Qnil;
        }

        private VALUE cTexture_fill(VALUE self, VALUE rbColor)
        {
            RUBY.rb_check_frozen(self);
            ITexture csTexture = this.GetTexture(self);
            if (csTexture == null)
            {
                RUBY.rb_raise(SERubyConsts.ForName("StarEngineError"), "disposed texture");
                return RUBY.Qnil;
            }
            csTexture.Fill(ColorClass.ToCSharpColor(rbColor));
            return RUBY.Qnil;
        }

        private VALUE cTexture_fill_rect(VALUE self, VALUE rbX, VALUE rbY, VALUE rbWidth, VALUE rbHeight, VALUE rbColor)
        {
            RUBY.rb_check_frozen(self);
            ITexture csTexture = this.GetTexture(self);
            if (csTexture == null)
            {
                RUBY.rb_raise(SERubyConsts.ForName("StarEngineError"), "disposed texture");
                return RUBY.Qnil;
            }

            int csX = RUBY.NUM2INT(rbX);
            int csY = RUBY.NUM2INT(rbY);
            int csWidth = RUBY.NUM2INT(rbWidth);
            int csHeight = RUBY.NUM2INT(rbHeight);
            Color csColor = ColorClass.ToCSharpColor(rbColor);
            csTexture.FillRectangle(new Rectangle(csX, csY, csWidth, csHeight), csColor);
            return RUBY.Qnil;
        }

        private VALUE cTexture_render_text(VALUE self, VALUE rbText, VALUE rbX, VALUE rbY, VALUE rbFont, VALUE rbColor)
        {
            RUBY.rb_check_frozen(self);
            ITexture csTexture = this.GetTexture(self);
            if (csTexture == null)
            {
                RUBY.rb_raise(SERubyConsts.ForName("StarEngineError"), "disposed texture");
                return RUBY.Qnil;
            }
            string csText = RUBY.StringValuePtr(rbText);
            Point csLocation = new Point(RUBY.NUM2INT(rbX), RUBY.NUM2INT(rbY));
            Font csFont = this.GetCachedObject<Font>(rbFont);
            Color csColor = ColorClass.ToCSharpColor(rbColor);
            csTexture.RenderText(csText, csLocation, csFont, csColor);
            return RUBY.Qnil;
        }


        private VALUE cTexture_cs_render_texture(VALUE self, VALUE rbTexture, VALUE rbX, VALUE rbY, VALUE rbSrcX, VALUE rbSrcY, VALUE rbSrcWidth, VALUE rbSrcHeight, VALUE rbScaleX, VALUE rbScaleY, VALUE rbAngle, VALUE rbCenterX, VALUE rbCenterY, VALUE rbAlpha, VALUE rbBlendType, VALUE rbTone)
        {
            RUBY.rb_check_frozen(self);
            ITexture csTexture = this.GetTexture(self);
            if (csTexture == null)
            {
                RUBY.rb_raise(SERubyConsts.ForName("StarEngineError"), "disposed texture");
                return RUBY.Qnil;
            }
            ITexture csTexture2 = this.GetTexture(rbTexture);
            if (csTexture2 == null)
            {
                RUBY.rb_raise(SERubyConsts.ForName("StarEngineError"), "disposed texture");
                return RUBY.Qnil;
            }

            int csX = RUBY.NUM2INT(rbX);
            int csY = RUBY.NUM2INT(rbY);

            int csSrcX = (rbSrcX == RUBY.Qnil) ? 0 : RUBY.NUM2INT(rbSrcX);
            int csSrcY = (rbSrcY == RUBY.Qnil) ? 0 : RUBY.NUM2INT(rbSrcY);
            int csSrcWidth = (rbSrcWidth == RUBY.Qnil) ? csTexture.Width : RUBY.NUM2INT(rbSrcWidth);
            int csSrcHeight = (rbSrcHeight == RUBY.Qnil) ? csTexture.Height : RUBY.NUM2INT(rbSrcHeight);
            Rectangle csSrcRect =
                new Rectangle(csSrcX, csSrcY, csSrcWidth, csSrcHeight);
            double csScaleX = (rbScaleX == RUBY.Qnil) ? 1.0 : RUBY.NUM2DBL(rbScaleX);
            double csScaleY = (rbScaleY == RUBY.Qnil) ? 1.0 : RUBY.NUM2DBL(rbScaleY);
            int csAngle = (rbAngle == RUBY.Qnil) ? 0 : RUBY.NUM2INT(rbAngle);
            double csCenterX = (rbCenterX == RUBY.Qnil) ? 0 : RUBY.NUM2DBL(rbCenterX);
            double csCenterY = (rbCenterY == RUBY.Qnil) ? 0 : RUBY.NUM2DBL(rbCenterY);
            byte csAlpha = (byte)((rbAlpha == RUBY.Qnil) ? 255 : Math.Min(255, Math.Max(0, RUBY.NUM2INT(rbAlpha))));
            BlendType csBlendType = (rbBlendType == RUBY.Qnil) ? BlendType.Alpha : BlendTypeEnum.ToCSharpBlendType(rbBlendType);
            Tone csTone = (rbTone == RUBY.Qnil) ? Tone.Empty : ToneClass.ToCSharpTone(rbTone);

            AffineMatrix mat = AffineMatrix.I;
            if (csScaleX != 1.0 || csScaleY != 1.0 || csAngle % Math4096.MaxAngle != 0)
            {
                mat.Concat(new AffineMatrix(1, 0, 0, 1, -csCenterX, -csCenterY));
                if (csScaleX != 1.0 || csScaleY != 1.0)
                    mat.Concat(new AffineMatrix(csScaleX, 0, 0, csScaleY, 0, 0));
                if (csAngle % Math4096.MaxAngle != 0)
                {
                    double cos = Math4096.Cos(csAngle);
                    double sin = Math4096.Sin(csAngle);
                    mat.Concat(new AffineMatrix(cos, -sin, sin, cos, 0, 0));
                }
                mat.Concat(new AffineMatrix(1, 0, 0, 1, csCenterX, csCenterY));
            }
            mat.Concat(new AffineMatrix(1, 0, 0, 1, csX, csY));

            try
            {
                csTexture.RenderTexture(csTexture2, csSrcRect, mat,
                    csAlpha, csBlendType, csTone);
            }
            catch (ArgumentException e)
            {
                RUBY.rb_raise(SERubyConsts.ForName("::ArgumentError"), e.Message);
            }

            return RUBY.Qnil;
        }
    }
}
