package org.cafebabepy.runtime.module.builtins;

import org.cafebabepy.runtime.CafeBabePyException;
import org.cafebabepy.runtime.PyObject;
import org.cafebabepy.runtime.Python;
import org.cafebabepy.runtime.module.AbstractCafeBabePyType;
import org.cafebabepy.runtime.module.DefinePyFunction;
import org.cafebabepy.runtime.module.DefinePyType;
import org.cafebabepy.runtime.object.iterator.PyStrIteratorObject;
import org.cafebabepy.runtime.object.java.PyStrObject;

import static org.cafebabepy.util.ProtocolNames.*;

/**
 * Created by yotchang4s on 2017/05/13.
 */
@DefinePyType(name = "builtins.str")
public final class PyStrType extends AbstractCafeBabePyType {

    public PyStrType(Python runtime) {
        super(runtime);
    }

    @DefinePyFunction(name = __str__)
    public PyObject __str__(PyObject self) {
        PyObject strType = this.runtime.typeOrThrow("builtins.str");

        if (!this.runtime.isInstance(self, strType)) {
            throw this.runtime.newRaiseTypeError("descriptor '__str__' requires a 'str' object but received a '" + self.getFullName() + "'");
        }

        return self;
    }

    @DefinePyFunction(name = __add__)
    public PyObject __add__(PyObject self, PyObject other) {
        PyObject strType = this.runtime.typeOrThrow("builtins.str");

        if (!(self instanceof PyStrObject)) {
            throw this.runtime.newRaiseTypeError(
                    "descriptor '__add__' requires a 'str' object but received a '" + self.getFullName() + "'");

        } else if (!(other instanceof PyStrObject)) {
            throw this.runtime.newRaiseTypeError("must be str, not " + other);
        }

        return ((PyStrObject) self).add((PyStrObject) other);
    }

    @DefinePyFunction(name = __iter__)
    public PyObject __iter__(PyObject self) {
        if (!(self instanceof PyStrObject)) {
            throw this.runtime.newRaiseTypeError(
                    "descriptor '__iter__' requires a 'str' object but received a '"
                            + self.getType().getFullName()
                            + "'");
        }

        return new PyStrIteratorObject(this.runtime, (PyStrObject) self);
    }

    @DefinePyFunction(name = __format__)
    public PyObject __format__(PyObject self, PyObject format_spec) {
        if (!this.runtime.isInstance(self, "str")) {
            throw this.runtime.newRaiseTypeError(
                    "descriptor '__format__' requires a 'str' object but received a '"
                            + self.getType().getFullName()
                            + "'");
        }

        // TODO format
        return self;
    }

    @DefinePyFunction(name = __eq__)
    public PyObject __eq__(PyObject self, PyObject other) {
        if (this.runtime.isInstance(self, "builtins.str")) {
            if (!(self instanceof PyStrObject)) {
                throw new CafeBabePyException("self is not PyStrObject " + self);

            } else if (this.runtime.isInstance(other, "builtins.str")) {
                if (!(other instanceof PyStrObject)) {
                    throw new CafeBabePyException("other is not PyStrObject " + other);
                }
                String jself = ((PyStrObject) self).getValue();
                String jother = ((PyStrObject) other).getValue();

                return jself.equals(jother) ? this.runtime.True() : this.runtime.False();

            } else {
                return this.runtime.NotImplemented();
            }

        } else {
            throw this.runtime.newRaiseTypeError("TypeError: descriptor '__eq__' requires a 'str' object but received a '" + self.getName() + "'");
        }
    }

    @DefinePyFunction(name = __hash__)
    public PyObject __hash__(PyObject self) {
        if (this.runtime.isInstance(self, "builtins.str")) {
            if (!(self instanceof PyStrObject)) {
                throw new CafeBabePyException("self is not PyStrObject " + self);
            }

            return this.runtime.number(((PyStrObject) self).getValue().hashCode());

        } else {
            throw this.runtime.newRaiseTypeError("TypeError: descriptor '__hash__' requires a 'str' object but received a '" + self.getName() + "'");
        }
    }

    @DefinePyFunction(name = "join")
    public PyObject join(PyObject self, PyObject iterable) {
        if (!this.runtime.isInstance(self, "builtins.str")) {
            throw this.runtime.newRaiseTypeError("TypeError: descriptor '__hash__' requires a 'str' object but received a '" + self.getName() + "'");
        }
        if (!(self instanceof PyStrObject)) {
            throw new CafeBabePyException("self is not PyStrObject " + self);
        }

        if (!this.runtime.isIterable(iterable)) {
            throw this.runtime.newRaiseTypeError("can only join an iterable");
        }

        String selfValue = ((PyStrObject) self).getValue();

        StringBuilder builder = new StringBuilder();
        this.runtime.iterIndex(iterable, (v, i) -> {
            if (i > 0) {
                builder.append(selfValue);
            }
            builder.append(v.toJava(String.class));
        });

        return this.runtime.str(builder.toString());
    }
}

@@ -461,16 +461,62 @@
 bject;%0A%0A
+import java.io.UnsupportedEncodingException;%0A%0A
 import s
@@ -5217,11 +5217,908 @@
 );%0A    %7D
+%0A%0A    @DefinePyFunction(name = %22encode%22)%0A    public PyObject encode(PyObject self, PyObject encoding) %7B%0A        if (!this.runtime.isInstance(self, %22str%22)) %7B%0A            throw this.runtime.newRaiseTypeError(%22 descriptor 'encode' requires a 'str' object but received a '%22 + self.getFullName() + %22'%22);%0A        %7D%0A        if (!this.runtime.isInstance(encoding, %22str%22)) %7B%0A            throw this.runtime.newRaiseTypeError(%22encode() argument 1 must be str, not %22 + getFullName());%0A        %7D%0A%0A        String value = self.toJava(String.class);%0A        String charsetName = encoding.toJava(String.class);%0A%0A        try %7B%0A            byte%5B%5D byteBytes = value.getBytes(charsetName);%0A%0A            return this.runtime.bytes(byteBytes);%0A%0A        %7D catch (UnsupportedEncodingException e) %7B%0A            throw this.runtime.newRaiseException(%22builtins.LookupError%22, %22unknown encoding: %22 + charsetName);%0A        %7D%0A    %7D
 %0A%7D%0A

Add str.encode